线程中的C#process.start()短暂冻结主线程
我们目前正在评估应用程序上一些缓慢的性能,并发现了一种奇怪的行为。
虽然该代码在Windows 7 / Server 2008 R2 / Server 2012 R2上运行良好,但从Server 2019 / Windows 10/11开始,我们会注意到奇怪的是在主线程上遇到了一个奇怪的问题。
所有使用.NET Framework 4.8的系统:
主线程在任务列表上迭代,在此列表中,对于任务启动了线程,几乎没有像这样简化:
foreach (CustomTaskObject task in this.Tasks){
task.ThreadObject.start();
}
每个Task-Object基本上包装CMD-Script或PowerShell脚本,所以任务目标是运行该脚本,转录输出,等待完成和日志。
螺纹对象基本上是这样创建的:
public class CustomTaskObject
public Thread ThreadObject;
public CustomTaskObject(){
this.ThreadObject = new Thread(new ThreadStart(new Action(() => {
Process p = new Process();
p.StartInfo.FileName = "powershell.exe"; //cmd.exe /C if a batch-script.
p.StartInfo.Arguments = "-ExecutionPolicy Bypass -f " + this.Folder + "\\" + this.File;
p.Start();
p.WaitForExit();
}));
}
删除了用于重定向输出 /读取 /记录的所有内容,它没有影响,无论我们使用Shellexecute true还是False,也无关紧要。
现在发生的事情是:
对于cmd-calls,一切运行都很好。如果将任务定义为可执行的异步,则主线程可以立即创建50个线程,主线程保持活动 /响应。< / p>
对于powerShell-calls:
p.start()
被调用,如果文件位于网络共享上,则该线程将持续约2-3秒。这并不令人惊讶,DNS分辨率,SMB Paket的东西,建筑连接 - 需要一些时间,都很好。
但是,真正奇怪的是:主线程也被困在调用task.threadObject.start();
上 - 直到启动线程中的过程。这使主线程在遍历工作对象的同时卡住了,您可以看到大约每3秒开始启动一个。
为了测试目的,我们在p.start()
- 猜猜是什么?在其中一个线程造成卡在之前,Mainthread能够启动30个这些线程……
public class CustomTaskObject
public Thread ThreadObject;
public CustomTaskObject(){
this.ThreadObject = new Thread(new ThreadStart(new Action(() => {
Process p = new Process();
p.StartInfo.FileName = "powershell.exe"; //cmd.exe if a ps-script.
p.StartInfo.Arguments = "-ExecutionPolicy Bypass -f " + this.Folder + "\\" + this.File;
System.Threading.Thread.Sleep(1000);
p.Start();
p.WaitForExit();
}));
}
那么,这里发生了什么?我只能假设,即使是从线程内启动的,start()即使从线程中启动,start()即使在某种程度上被卸载到主线程中?因此,如果我们将其延迟1000ms,则主线程可以先完成迭代,然后卡住(不可见)以启动进程。
- 为什么仅对上述操作系统发生这种情况?
- 为什么它以
cmd.exe
以相同的方式定位网络文件完美无瑕?
我什至可以使用以下控制台应用程序重现:
从输出中,您可以看到第一次到达p.start()
的位置 - 导致在这里大约1秒钟,总结,随着更多p.starts()
的线程达到。
class Program
{
static Thread[] threads = new Thread[30];
static void Main(string[] args)
{
for (int i = 0; i < 30; i++)
{
threads[i] = new Thread(new ThreadStart(new Action(() =>
{
Process p = new Process();
p.StartInfo.FileName = "powershell.exe";
p.StartInfo.Arguments = @"-ExecutionPolicy Bypass -f \\server\share\ping.ps1";
p.Start();
p.WaitForExit();
})));
}
for (int i = 0; i < 30; i++)
{
Console.WriteLine(DateTime.Now.ToLongTimeString() + " Starting thread " + i);
threads[i].Start();
Console.WriteLine(DateTime.Now.ToLongTimeString() + " Thread started " + i);
}
Console.ReadKey();
}
}
输出:
17:30:20 Starting thread 0
17:30:20 Thread started 0
17:30:20 Starting thread 1
17:30:20 Thread started 1
17:30:20 Starting thread 2
17:30:20 Thread started 2
17:30:20 Starting thread 3
17:30:20 Thread started 3
17:30:20 Starting thread 4
17:30:20 Thread started 4
17:30:20 Starting thread 5
17:30:20 Thread started 5
17:30:20 Starting thread 6
17:30:20 Thread started 6
17:30:20 Starting thread 7
17:30:20 Thread started 7
17:30:20 Starting thread 8
17:30:20 Thread started 8
17:30:20 Starting thread 9
17:30:20 Thread started 9
17:30:20 Starting thread 10
17:30:20 Thread started 10
17:30:20 Starting thread 11
17:30:25 Thread started 11
17:30:25 Starting thread 12
17:30:29 Thread started 12
17:30:29 Starting thread 13
17:30:31 Thread started 13
17:30:31 Starting thread 14
17:30:33 Thread started 14
17:30:33 Starting thread 15
17:30:33 Thread started 15
17:30:33 Starting thread 16
17:30:36 Thread started 16
17:30:36 Starting thread 17
17:30:37 Thread started 17
17:30:37 Starting thread 18
17:30:40 Thread started 18
17:30:40 Starting thread 19
17:30:41 Thread started 19
17:30:41 Starting thread 20
17:30:42 Thread started 20
17:30:42 Starting thread 21
17:30:44 Thread started 21
17:30:44 Starting thread 22
17:30:45 Thread started 22
17:30:45 Starting thread 23
17:30:46 Thread started 23
17:30:46 Starting thread 24
17:30:47 Thread started 24
17:30:47 Starting thread 25
17:30:48 Thread started 25
17:30:48 Starting thread 26
17:30:49 Thread started 26
17:30:49 Starting thread 27
17:30:50 Thread started 27
17:30:50 Starting thread 28
17:30:52 Thread started 28
17:30:52 Starting thread 29
17:30:53 Thread started 29
当然,除非您拥有30个核心CPU,否则不能同时执行30个线程 - 但是如何“卡住” Windows Server 2019 / Windows 10&amp; amp; 11?
更新了测试标记以访问网络共享。在11个线程后,实际达到了第一个p.start()
,从而在主线程上大量卡住。
在20个核心上运行,我可以假设主线程没有被悬挂。看来整个卡住的完全位于适当的位置,直到PowerShell已解决并加载该文件为止。然后,主线程将继续,而脚本在其过程中正常运行时。
有趣的观察:虽然主线程陷入困境,但即使是Visual Studios Profiler都在卡住...(但是在运行App Antial时也会发生这种情况,而无需附加调试器)
We are currently evaluating some slowish performance on an application, and found a strange behaviour.
While the code runs perfectly fine on Windows 7 / Server 2008 R2 / Server 2012 R2, starting with Server 2019 / Windows 10/11, we can notice a strange stuck on the Main-Thread.
All Systems using .net Framework 4.8:
The main-thread is iterating over a list of tasks, where foreach task a thread is started, hardly simplified like this:
foreach (CustomTaskObject task in this.Tasks){
task.ThreadObject.start();
}
Each Task-Object wraps basically either a cmd-script or a powershell script, so the tasks objective is to run that script, transscript the output, wait for finish and log.
The ThreadObject is basically created like this:
public class CustomTaskObject
public Thread ThreadObject;
public CustomTaskObject(){
this.ThreadObject = new Thread(new ThreadStart(new Action(() => {
Process p = new Process();
p.StartInfo.FileName = "powershell.exe"; //cmd.exe /C if a batch-script.
p.StartInfo.Arguments = "-ExecutionPolicy Bypass -f " + this.Folder + "\\" + this.File;
p.Start();
p.WaitForExit();
}));
}
All the stuff for redirecting output / reading / logging has been removed, it has no impact, also it doesn't matter if we use ShellExecute True or false.
What happens now is:
For cmd-calls, everything runs nice and smooth. If Tasks are defined to be executable asynchronous, the main thread can create 50 threads in no time, Main-Thread stays active / responsive.
For powershell-calls: As soon as
p.Start()
is called, the THREAD is stucking about 2-3 Seconds, if the file is located on a network share. This is no big surprise, dns resolution, smb paket stuff, building connections - takes some time, all good.
However, what is really strange: The Main-Thread is stuck at calling task.ThreadObject.start();
as well - Until the Process in the thread is started. This makes the main thread stuck while iterating through the job-objects, and you can see that one is started about every 3 seconds.
For testing purpose we added a Thread.Sleep(1000)
before p.Start()
- and guess what? The mainthread was able to kick off 30 these threads, before one of them was causing a stuck...
public class CustomTaskObject
public Thread ThreadObject;
public CustomTaskObject(){
this.ThreadObject = new Thread(new ThreadStart(new Action(() => {
Process p = new Process();
p.StartInfo.FileName = "powershell.exe"; //cmd.exe if a ps-script.
p.StartInfo.Arguments = "-ExecutionPolicy Bypass -f " + this.Folder + "\\" + this.File;
System.Threading.Thread.Sleep(1000);
p.Start();
p.WaitForExit();
}));
}
So, what is happening here? I can only assume, that calling process.Start() is in someway offloaded to the Main-Thread, even if started from within a thread? Hence, If we delay that by 1000ms, the main thread can finish iteration first, then get stuck (invisible) to start processes.
- Why does this only happen for the mentioned operating systems?
- Why does it run flawless for
cmd.exe
targeting network files in the very same way?
I can even reproduce this with the following console-Application:
From the output, you can see, where the first time p.Start()
is reached - leading to stucks of about 1 second here, summing up, as more p.starts()
are reached by their threads.
class Program
{
static Thread[] threads = new Thread[30];
static void Main(string[] args)
{
for (int i = 0; i < 30; i++)
{
threads[i] = new Thread(new ThreadStart(new Action(() =>
{
Process p = new Process();
p.StartInfo.FileName = "powershell.exe";
p.StartInfo.Arguments = @"-ExecutionPolicy Bypass -f \\server\share\ping.ps1";
p.Start();
p.WaitForExit();
})));
}
for (int i = 0; i < 30; i++)
{
Console.WriteLine(DateTime.Now.ToLongTimeString() + " Starting thread " + i);
threads[i].Start();
Console.WriteLine(DateTime.Now.ToLongTimeString() + " Thread started " + i);
}
Console.ReadKey();
}
}
Output:
17:30:20 Starting thread 0
17:30:20 Thread started 0
17:30:20 Starting thread 1
17:30:20 Thread started 1
17:30:20 Starting thread 2
17:30:20 Thread started 2
17:30:20 Starting thread 3
17:30:20 Thread started 3
17:30:20 Starting thread 4
17:30:20 Thread started 4
17:30:20 Starting thread 5
17:30:20 Thread started 5
17:30:20 Starting thread 6
17:30:20 Thread started 6
17:30:20 Starting thread 7
17:30:20 Thread started 7
17:30:20 Starting thread 8
17:30:20 Thread started 8
17:30:20 Starting thread 9
17:30:20 Thread started 9
17:30:20 Starting thread 10
17:30:20 Thread started 10
17:30:20 Starting thread 11
17:30:25 Thread started 11
17:30:25 Starting thread 12
17:30:29 Thread started 12
17:30:29 Starting thread 13
17:30:31 Thread started 13
17:30:31 Starting thread 14
17:30:33 Thread started 14
17:30:33 Starting thread 15
17:30:33 Thread started 15
17:30:33 Starting thread 16
17:30:36 Thread started 16
17:30:36 Starting thread 17
17:30:37 Thread started 17
17:30:37 Starting thread 18
17:30:40 Thread started 18
17:30:40 Starting thread 19
17:30:41 Thread started 19
17:30:41 Starting thread 20
17:30:42 Thread started 20
17:30:42 Starting thread 21
17:30:44 Thread started 21
17:30:44 Starting thread 22
17:30:45 Thread started 22
17:30:45 Starting thread 23
17:30:46 Thread started 23
17:30:46 Starting thread 24
17:30:47 Thread started 24
17:30:47 Starting thread 25
17:30:48 Thread started 25
17:30:48 Starting thread 26
17:30:49 Thread started 26
17:30:49 Starting thread 27
17:30:50 Thread started 27
17:30:50 Starting thread 28
17:30:52 Thread started 28
17:30:52 Starting thread 29
17:30:53 Thread started 29
Sure, 30 threads cannot be executed at the same time, unless you have a 30 core cpu - but how can it "stuck" the Main-Thread on Windows Server 2019 / Windows 10 & 11?
Updated the test-script to access a network share. After 11 threads, the first p.start()
is actually reached, leading to significant stucks on the main-thread.
Running on a 20 core, I can assume the main-thread didnt get suspended. It seems the whole stuck is exactly in place, until powershell has resolved and loaded that file. Then the main thread continues, while the script is running in its process as it is supposed to be.
Funny observation: While the Main-Thread is stucking, even Visual Studios profiler is stucking... (But it also happens when running the app native, without debugger attached)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
事实证明 - 适当的解决方案有时比解决方法更好。
该过程在线程中的启动与陷入的主线程之间确实存在连接 - 但是由于这不是在Windows 7机器上发生的,而不是出于“ CMD” - 启动,所以我检查了“慢速PowerShell”的原因完全开始。
是卡斯珀基(Kasperky),他将每次呼叫推迟到powershell.exe降至2-4秒。关闭小丑,然后立即
p.start()
立即返回,然后又不再引起任何明显的卡在主线程上。猜猜这是生命威胁的一部分,卡巴斯基在其中“冻结”了所谓的应用程序(powershell)以及来电者堆栈(process.start,start,main-thearread,to Visual Studio),直到它弄清楚了一切看起来很严重。
Turns out - a proper solution is sometimes better than a workaround.
There is indeed a connection between the process start in the thread and a stucking main-thread - but since this didn't happen on Windows 7 machines and not for "cmd"-starts, I checked for the reason of the "slow powershell" start at all.
It has been Kasperky, who was delaying each call to powershell.exe by 2-4 seconds. Shutting down the clown, and immediately
p.start()
returns in no time, which in turn did then no longer cause any noticable stucks on the main-thread.Guess this is part of the Live-Threat-Defence, where Kaspersky was "freezing" the called application (powershell) as well as the caller-stack (process.start, main-thread, upto visual studio), until it figured that everything looks serious.
我在这里找到了一个类似的问题:
process.start()在背景线程上运行时悬挂一个>
在这里
通过process.
所以,我对此进行了测试,这很有趣。使用
平稳运行到完全达到20个线程(获得20个核心CPU) - 然后主线程卡住,直到第一个孩子完成。但这是一种更加期望的行为,无论如何我通常不会比芯1启动更多的线程。
因此,将这两种内容整合在一起,它与应用程序std输出流有关,并且每个启动过程也与之相互作用,从而导致
process.start()
在process.start()中都陷入了链接。 (哪个
cmd /c start < /code>正在避免使用),
如果可以以“更好”的方式避免这种情况,我将在以后进行更多测试,但是我担心只要我想要该过程的输出流蜜蜂被抓住了,几乎没有空间...也许只是刺入的东西,例如吹牛到坦率的人,然后读书。
I've found a similiar issue here:
Process.Start() hangs when running on a background thread
and here
Process spawned through Process.Start in .NET hangs the thread
So, I tested this, and it is interesting. Using
runs smooth upto exactly 20 threads (got a 20 core cpu) - then the main-thread stucks until a first child completes. But that is a more expected behavior, and I'm usually not launching more threads than cores-1 anyway.
So, putting both things together, it has to do with the applications std output stream, and that each started process interacts with that as well, causing even the mainthread to stuck during
process.start()
. (Whichcmd /C start
is avoiding)I'll do some more testing later if this can be avoided in "nicer" ways, but I fear that as long as i want the output stream of that process beeing catched, there is little room... Maybe just something hacky like piping to a tempfile and reading afterwards.