dotnet 6 创建进程 Process.Start 时设置 UseShellExecute 在 Windows 下对性能的影响
本文将告诉大家,在 dotnet 6 或 dotnet 7 版本里,启动新的进程时,在 StartInfo 设置 UseShellExecute 为 true 和 false 时,对性能的影响
在 dotnet 6 或 dotnet 7 版本里,其他的版本我没有测试和去了解哈,启动新的进程时,在 StartInfo 设置 UseShellExecute 为 true 时,且当调用线程非 STA 时,在 Windows 下,性能会较差
为什么性能会比较差?下面将从 dotnet 源代码的角度来告诉大家
开始之前,回顾一下 UseShellExecute 属性的作用,在 Process.Start 里,是允许调用 Shell 打开进程的,传入的不一定要求是一个 exe 等可执行文件,还可以是某个文件,例如 txt 文件。传入文件时,系统将会根据默认打开程序,使用文件的默认打开程序打开文件,例如 txt 文件默认将使用记事本程序打开。想要实现此效果,就需要将 UseShellExecute 设置为 true 的值
设置为 true 的值,在 dotnet 底层将会调用 win32 的 ShellExecuteExW 函数
而对于打开某个 exe 来说,很多时候,除非是需要加上 verb 等,否则也是不需要用到 ShellExecuteExW 启动的。换句话说,如果明确知道是启动一个进程,只是启动时传传参数等,且没有其他的需求,可以放心设置 UseShellExecute 为 false 的值,当然,为 false 也是默认值
为什么将 UseShellExecute 设置为 true 的性能比较差?这还需要从 dotnet 的调用 ShellExecuteExW 函数方法开始聊起
在 dotnet 的 Process.Start 方法里面,有许多重载方法,最终都会调进去 public bool Start() 方法里面,在此方法里,将进入平台有关的 StartCore 方法
这里只讨论 Windows 下的 StartCore 方法的实现,其实现是根据 Windows 下的创建进程使用的 CreateProcessW 和 ShellExecuteExW 函数的不同从而需要判断 UseShellExecute 属性来决定调用哪个方法
public partial class Process : IDisposable
{
... // 忽略其他代码
private bool StartCore(ProcessStartInfo startInfo)
{
return startInfo.UseShellExecute
? StartWithShellExecuteEx(startInfo)
: StartWithCreateProcess(startInfo);
}
... // 忽略其他代码
}
先来看看 StartWithCreateProcess 方法吧,这个方法比较简单,省略的代码如下
public partial class Process : IDisposable
{
... // 忽略其他代码
private unsafe bool StartWithCreateProcess(ProcessStartInfo startInfo)
{
if (startInfo.UserName.Length != 0)
{
... // 忽略其他代码
retVal = Interop.Advapi32.CreateProcessWithLogonW(
startInfo.UserName,
startInfo.Domain,
(passwordPtr != IntPtr.Zero) ? passwordPtr : (IntPtr)passwordInClearTextPtr,
logonFlags,
null, // we don't need this since all the info is in commandLine
commandLinePtr,
creationFlags,
(IntPtr)environmentBlockPtr,
workingDirectory,
ref startupInfo, // pointer to STARTUPINFO
ref processInfo // pointer to PROCESS_INFORMATION
);
}
else
{
... // 忽略其他代码
retVal = Interop.Kernel32.CreateProcess(
null, // we don't need this since all the info is in commandLine
commandLinePtr, // pointer to the command line string
ref unused_SecAttrs, // address to process security attributes, we don't need to inherit the handle
ref unused_SecAttrs, // address to thread security attributes.
true, // handle inheritance flag
creationFlags, // creation flags
(IntPtr)environmentBlockPtr, // pointer to new environment block
workingDirectory, // pointer to current directory name
ref startupInfo, // pointer to STARTUPINFO
ref processInfo // pointer to PROCESS_INFORMATION
);
}
... // 忽略其他代码
}
... // 忽略其他代码
}
在 dotnet 代码里面看到 StartWithCreateProcess 方法需要的代码很多,但其实只是调用 win32 方法比较繁琐而已
接下来看看 StartWithShellExecuteEx 方法的实现,通过这个方法的实现就可以知道为什么在 Windows 下,设置 UseShellExecute 为 true 且当调用线程非 STA 时,性能会较差的原因
public partial class Process : IDisposable
{
... // 忽略其他代码
private unsafe bool StartWithShellExecuteEx(ProcessStartInfo startInfo)
{
... // 忽略其他代码
ShellExecuteHelper executeHelper = new ShellExecuteHelper(&shellExecuteInfo);
if (!executeHelper.ShellExecuteOnSTAThread())
{
... // 忽略其他代码
}
... // 忽略其他代码
}
... // 忽略其他代码
}
可以看到在 StartWithShellExecuteEx 里使用的是 ShellExecuteHelper 辅助方法来实现,通过 ShellExecuteOnSTAThread 也能猜到,这是在 STA 线程执行的。这是因为启动线程如果是用来调用文件打开,一些 COM 是需要 STA 线程的。然而如果当前的线程不是 STA 的线程,那需要如何执行
接下来继续看 ShellExecuteOnSTAThread 的实现
internal unsafe class ShellExecuteHelper
{
... // 忽略其他代码
public bool ShellExecuteOnSTAThread()
{
// ShellExecute() requires STA in order to work correctly.
if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA)
{
ThreadStart threadStart = new ThreadStart(ShellExecuteFunction);
Thread executionThread = new Thread(threadStart)
{
IsBackground = true,
Name = ".NET Process STA"
};
executionThread.SetApartmentState(ApartmentState.STA);
executionThread.Start();
executionThread.Join();
}
else
{
ShellExecuteFunction();
}
... // 忽略其他代码
return _succeeded;
}
private void ShellExecuteFunction()
{
try
{
if (!(_succeeded = Interop.Shell32.ShellExecuteExW(_executeInfo)))
ErrorCode = Marshal.GetLastWin32Error();
}
catch (EntryPointNotFoundException)
{
_notpresent = true;
}
}
... // 忽略其他代码
}
可以看到在 dotnet 里面,判断当前线程,如果不是 STA 线程,那就再启动一个 STA 线程去执行代码,而且是等待启动的 STA 线程执行完成再方法,同步等待另一个线程。这就是为什么性能比较差的原因,性能差在需要启动线程和等待线程执行完成
那有伙伴说,那是不是每次都放在客户端的 STA 主线程调用好了,这样就让 dotnet 底层不需要启动新的线程?其实这不好,因为 ShellExecuteExW 这个 win32 方法不是非常快速的,在一些系统上,将会等待很长时间,特别是有 360 等的情况,如果在主线程被进入等待,那自然是不如多开一个后台线程
看完了原理之后,相信大家也就知道,如果明确知道是启动一个进程,只是启动时传传参数等,且没有其他的需求,可以放心设置 UseShellExecute 为 false 的值,当然,为 false 也是默认值,这样性能会更高
dotnet 6 创建进程 Process.Start 时设置 UseShellExecute 在 Windows 下对性能的影响的更多相关文章
- 设置 Quick-Cocos2d-x 在 Windows 下的编译环境
http://cn.cocos2d-x.org/tutorial/show?id=1304 设置 Quick-Cocos2d-x 在 Windows 下的编译环境 Liao Yulei2014-08- ...
- 同步异步、mutiprocessing创建进程process模块及进程对象的多种方法、消息队列Queue
目录 同步异步 阻塞与非阻塞 综合使用 创建进程的多种方式之multiprocess.process模块 进程间数据隔离 进程的join方法 IPC机制 生产者 消费者模型 进程对象的多种方法 守护进 ...
- wampserver-----------如何设置wampserver在windows下开机自动启动。
虽然很简单,但是还是做个记录.我的习惯,还是看图: 到你电脑的服务里面找到这两项然后点击右键属性,设置为自动.
- SQL Server在执行SQL语句时,表之间驱动顺序对性能的影响
环境:SQL Server2012 SP3 企业版,开发服务器,并没有什么负载,全库索引统一Rebuild过 经反复执行验证过, 不算太复杂的SQL(存储过程中代入参数抠出来的SQL代码) 默认情况下 ...
- windows下mongodb基础玩法系列二CURD操作(创建、更新、读取和删除)
windows下mongodb基础玩法系列 windows下mongodb基础玩法系列一介绍与安装 windows下mongodb基础玩法系列二CURD操作(创建.更新.读取和删除) windows下 ...
- Linux和Windows下的进程管理总结
在Windows和Linux下都可以很方便地列出当前运行的进程.Windows下可以使用组合键CTRL+ALT+DEL打开任务管理器,在进程选项卡中就列举出了当前运行的所有进程,除此之外还可以在命令行 ...
- 一起talk C栗子吧(第一百三十三回:C语言实例--创建进程时的内存细节)
各位看官们.大家好,上一回中咱们说的是从内存角度看进程和线程的样例.这一回咱们说的样例是:创建进程时的内存细节.闲话休提,言归正转.让我们一起talk C栗子吧! 看官们.我们都知道使用fork函数能 ...
- 使用Process子类创建进程
#_author:来童星#date:2019/12/17# 使用Process子类创建进程from multiprocessing import Processimport timeimport os ...
- 阿里巴巴Java开发手册建议创建HashMap时设置初始化容量,但是多少合适呢?
集合是Java开发日常开发中经常会使用到的,而作为一种典型的K-V结构的数据结构,HashMap对于Java开发者一定不陌生. 关于HashMap,很多人都对他有一些基本的了解,比如他和hashtab ...
- Java并发编程:如何创建进程?
转载自:http://www.cnblogs.com/dolphin0520/p/3913517.html 在前面一篇文章中已经讲述了在进程和线程的由来,今天就来讲一下在Java中如何创建线程,让线程 ...
随机推荐
- npm ERR! code 1 npm ERR! path E:\20231213\vue-element-admin\node_modules\node-sass npm ERR! command failed npm ERR! command C:\WINDOWS\system32\cmd.exe /d /s /c node scripts/build.js
执行npm install报错,根据下面报错信息可知,是由于nodejs和node-sass版本不一致造成的,也就是当前项目比较旧,而我安装的nodejs比较新. PS E:\20231213\vue ...
- 更智能的广告素材生成!看A/B测试如何驱动AIGC素材调优
更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 前言:AIGC大爆发,引发广告营销行业变革 ChatGPT等AI产品引发的AIGC大爆发引起了各行业的震动,其中以 ...
- TypeScript筑基笔记一:Visual Studio Code 创建Typescript文件和实时监控
问题一:电脑如何安装Typescript? 答案:打开电脑cmd 输入以下指令: npm install -g typescript 中国电脑因为访问慢,可以先安装cnpm后再安装 安装cnpm指令 ...
- Linux_Centos 增加中文字体支持
宋体黑体为例 1.安装字体库 在CentOS 4.x开始用fontconfig来安装字体库,所以输入以下命令即可: sudo yum -y install fontconfig 这时在/usr/sha ...
- 一文讲透Java核心技术之高可扩展利器SPI
大家好,我是冰河~~ SPI的概念 JAVA SPI = 基于接口的编程+策略模式+配置文件 的动态加载机制 SPI的使用场景 Java是一种面向对象语言,虽然Java8开始支持函数式编程和Strea ...
- virtualbox安装windows10出现OOBE,卡在OOBE。
参照 https://zhuanlan.zhihu.com/p/419237209 https://www.0z.gs/win/781.html 文档 https://learn.microsoft. ...
- Echarts世界地图和网页表格数据交互联动
html布局: 1 <div class="column"> 2 <div class="panel bl bar1"> 3 <d ...
- set集合的TreeSet类
TreeSet 类 TreeSet类是Set接口的一个实现类,主要作用是用于对对象的排序以及确定存入对象的唯一性. 它的底层是红黑树,用来确保元素的排序和唯一性的,基于map对象,TreeSet是对T ...
- #决策单调性dp,分治#LOJ 6039「雅礼集训 2017 Day5」珠宝
题目传送门 分析 观察到这个0/1背包中单个物品的体积不超过300,考虑分体积考虑. 设 \(dp[i]\) 表示容量大小为 \(i\) 的背包能获得的最大价值, \(dp[i]=\max\{dp[i ...
- C#-GroupBox包含控件,如何获取这些控件的名称
您可以使用 Enumerable.OfType在GroupBox中查找和投射您的RadioButtons: var radioButtons = groupBox1.Controls.OfType&l ...