Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
933 views
in Technique[技术] by (71.8m points)

c# - Freeze when programmatically launching a batch file (with output redirection), when the batch file simply calls "start some.exe"

EDIT: Found some duplicates with no answers:

  1. Issue with output redirection in batch
  2. How do I use the "start" command without inheriting handles in the child process?

I have some C# code that tries to be a generic process launcher, within a larger long-running program. This code needs to capture the output from the processes it launches, and also wait for them to finish. We typically launch batch files with this code, and everything works fine, except when we want to start another child process from inside the batch file, such that it will outlive the batch file process. As an example, let's say I simply want to execute "start notepad.exe" from inside the batch file.

I encountered the same problems as in this question: Process.WaitForExit doesn't return even though Process.HasExited is true

Basically, even though the batch file process appears to be exiting very quickly (as expected), my program freezes until the child process (eg. notepad) also exits. However, notepad is not meant to exit, in my scenario.

I tried injecting "cmd.exe /C" at all points in the chain, with no luck. I tried explicitly terminating the batch file with "exit" or "exit /B". I tried reading the output both synchronously and asynchronously - with or without worker threads. I tried the patterns here: https://github.com/alabax/CsharpRedirectStandardOutput/tree/master/RedirectStandardOutputLibrary (see FixedSimplePattern.cs and AdvancedPattern.cs), again with no luck.

EDIT: I also tried with some C# P/Invoke code that does the process launching via the Windows API (CreatePipe/CreateProcess, etc), so I don't think this problem is specific to the C# Process API.

The only workaround I found was to replace the start command with a tool that calls CreateProcess with the DETACHED_PROCESS flag (CREATE_NO_WINDOW also works).

The accepted answer in the aforementioned SO question (https://stackoverflow.com/a/26722542/5932003) is the closest thing in the entire Internet that would appear to work, but it turns out it's leaking threads with every batch file you launch. I would have left a comment there, but I don't have the reputation to do that yet :).

Modified code that demonstrates thread leakage:

using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace TestSO26713374WaitForExit
{
    class Program
    {
    static void Main(string[] args)
    {
        while(true)
        {
            string foobat =
@"@echo off
START ping -t localhost
REM START ping -t google.com
REM ECHO Batch file is done!
EXIT /B 123
";

            File.WriteAllText("foo.bat", foobat);

            Process p = new Process
            {
                StartInfo =
                new ProcessStartInfo("foo.bat")
                {
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true
                }
            };

            p.Start();

            var _ = ConsumeReader(p.StandardOutput);
            _ = ConsumeReader(p.StandardError);

            //Console.WriteLine("Calling WaitForExit()...");
            p.WaitForExit();
            //Console.WriteLine("Process has exited. Exit code: {0}", p.ExitCode);
            //Console.WriteLine("WaitForExit returned.");


            ThreadPool.GetMaxThreads(out int max, out int max2);
            ThreadPool.GetAvailableThreads(out int available, out int available2);

            Console.WriteLine(
                $"Active thread count: {max - available} (thread pool), {System.Diagnostics.Process.GetCurrentProcess().Threads.Count} (all).");

            Thread.Sleep(8000);
        }
    }

    async static Task ConsumeReader(TextReader reader)
    {
        string text;

        while ((text = await reader.ReadLineAsync()) != null)
        {
            Console.WriteLine(text);
        }
    }
}
}

The output from the above:

Active thread count: 2 (thread pool), 15 (all).
Active thread count: 4 (thread pool), 18 (all).
Active thread count: 6 (thread pool), 19 (all).
Active thread count: 8 (thread pool), 20 (all).
Active thread count: 9 (thread pool), 21 (all).
Active thread count: 11 (thread pool), 23 (all).
Active thread count: 13 (thread pool), 25 (all).
Active thread count: 15 (thread pool), 27 (all).
Active thread count: 17 (thread pool), 29 (all).
Active thread count: 19 (thread pool), 31 (all).
Active thread count: 21 (thread pool), 33 (all).
...

My questions:

  1. Why doesn't the start command completely break the chain of output redirection?
  2. Am I stuck with the aforementioned tool that calls CreateProcess(...DETACHED_PROCESS...)?

Thanks!

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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...).


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...