本文将告诉大家,在 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 也是默认值,这样性能会更高
出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits
我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta
我对最新版本的Rails有疑问。我创建了一个新应用程序(railsnewMyProject),但我没有脚本/生成,只有脚本/rails,当我输入ruby./script/railsgeneratepluginmy_plugin"Couldnotfindgeneratorplugin.".你知道如何生成插件模板吗?没有这个命令可以创建插件吗?PS:我正在使用Rails3.2.1和ruby1.8.7[universal-darwin11.0] 最佳答案 随着Rails3.2.0的发布,插件生成器已经被移除。查看变更日志here.现在
在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',
如何使用RSpec::Core::RakeTask初始化RSpecRake任务?require'rspec/core/rake_task'RSpec::Core::RakeTask.newdo|t|#whatdoIputinhere?endInitialize函数记录在http://rubydoc.info/github/rspec/rspec-core/RSpec/Core/RakeTask#initialize-instance_method没有很好的记录;它只是说:-(RakeTask)initialize(*args,&task_block)AnewinstanceofRake
我正在编写一个gem,我必须在其中fork两个启动两个webrick服务器的进程。我想通过基类的类方法启动这个服务器,因为应该只有这两个服务器在运行,而不是多个。在运行时,我想调用这两个服务器上的一些方法来更改变量。我的问题是,我无法通过基类的类方法访问fork的实例变量。此外,我不能在我的基类中使用线程,因为在幕后我正在使用另一个不是线程安全的库。所以我必须将每个服务器派生到它自己的进程。我用类变量试过了,比如@@server。但是当我试图通过基类访问这个变量时,它是nil。我读到在Ruby中不可能在分支之间共享类变量,对吗?那么,还有其他解决办法吗?我考虑过使用单例,但我不确定这是
关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?
我正在阅读SandiMetz的POODR,并且遇到了一个我不太了解的编码原则。这是代码:classBicycleattr_reader:size,:chain,:tire_sizedefinitialize(args={})@size=args[:size]||1@chain=args[:chain]||2@tire_size=args[:tire_size]||3post_initialize(args)endendclassMountainBike此代码将为其各自的属性输出1,2,3,4,5。我不明白的是查找方法。当一辆山地自行车被实例化时,因为它没有自己的initialize方法