Freeze when programmatically launching a batch file (with output redirection), when the batch file simply calls “start some.exe”

╄→尐↘猪︶ㄣ 提交于 2019-11-29 15:25:28

Here's a drop-in replacement for the start command:

start /b powershell.exe Start-Process -FilePath "notepad.exe"

"start /b" is only there to start powershell without showing its window (which would otherwise flash for a second - annoying). Powershell takes over after that and launches our process, without any of the side-effects.

If powershell isn't an option, it turns out that this super simple C# program can serve the same purpose:

using System.Diagnostics;

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            // You may want to expand this to set CreateNoWindow = true
            Process.Start(args[0]);
        }
    }
}

No need for CreateProcess(...DETACHED_PROCESS...).

I don't have an answer for the first question. That's more of a fundamental design or implementation detail of Windows, and I can't speak to why they decided to do it that way.

As for the second question…

Unfortunately, this appears to be a limitation of the Process class, due to the way it was implemented.

Using the asynchronous reading from the stdout and stderr streams resolves the WaitForExit() issue, but doesn't do anything to address the underlying way Windows ties the parent process to the child process. The parent's output streams (which are redirected) won't be closed until the child exits, and so there are still outstanding reads in the C# program on the parent's output streams, waiting for those streams to be closed.

In the Process class, for redirected output, it wraps the I/O stream handle in a FileStream object, and when it creates this object, it does not create it with the async flag set to true, which would be required for the object to use IOCP for asynchronous operations. It doesn't even create the underlying native pipe object with IOCP support.

So, when the code issues an asynchronous read on the stream, this is implemented in .NET by "faking" asynchrony. That is, it just queues a synchronous read on the regular thread pool.

So you get a new active thread pool thread for every outstanding read, two per process. And the last reads from the output streams won't return until the child process exits, tying up those thread pool threads.

I don't see any great way to avoid this.

For a process which you know will have a very tiny amount of output on the stdout and stderr streams (or none at all…in the example at hand, nothing at all is written to stderr, for example), you could just not read the streams. But the buffer for each of those streams is not very large, so failing to read them will generally cause the process to eventually block.

In some special cases, you could arrange things so that you don't read from the streams when you know you're at the end. For example, in the code above, you could uncomment the "Batch file is done!" and terminate the ConsumeReader() loop when you see that text. But that's not a solution that will work in many cases. It would rely on knowing exactly what is going to be written, at least for the very last line, of both the stdout and stderr streams.

For what it's worth, at least I'd say that you're not literally leaking threads. Each thread is in fact doing something, and each thread will in fact finally be freed up, once the associated process does exit. And frankly, on Windows, a thread costs a lot less than a process, so if you have enough child processes running concurrently that you're worried about the number of threads in your one process, you've probably got bigger fish to fry (i.e. all those child processes eating up resources).

After investigating what I could, I think that your current solution, a tool used in lieu of the start command to create your process as a detached child, is probably the most elegant. The only reliable alternative would be for you to effectively re-implement the Process class itself (or at least, the parts you are using here), except with IOCP support so that reads on the standard streams are in fact implemented asynchronously.

I suppose you could try to report the issue to Microsoft, but I'd guess at this point, they've no interest in making any changes to the Process class.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!