ProcessStartInfo 挂在“WaitForExit”上? 为什么?

发布于 2024-07-06 18:01:52 字数 711 浏览 8 评论 0原文

我有以下代码:

info = new System.Diagnostics.ProcessStartInfo("TheProgram.exe", String.Join(" ", args));
info.CreateNoWindow = true;
info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
info.RedirectStandardOutput = true;
info.UseShellExecute = false;
System.Diagnostics.Process p = System.Diagnostics.Process.Start(info);
p.WaitForExit();
Console.WriteLine(p.StandardOutput.ReadToEnd()); //need the StandardOutput contents

我知道我正在启动的进程的输出大约 7MB 长。 在 Windows 控制台中运行它效果很好。 不幸的是,以编程方式,这会无限期地挂在 WaitForExit 处。 另请注意,对于较小的输出(例如 3KB),此代码不会挂起。

是否有可能ProcessStartInfo中的内部StandardOutput无法缓冲7MB? 如果是这样,我该怎么办? 如果不是,我做错了什么?

I have the following code:

info = new System.Diagnostics.ProcessStartInfo("TheProgram.exe", String.Join(" ", args));
info.CreateNoWindow = true;
info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
info.RedirectStandardOutput = true;
info.UseShellExecute = false;
System.Diagnostics.Process p = System.Diagnostics.Process.Start(info);
p.WaitForExit();
Console.WriteLine(p.StandardOutput.ReadToEnd()); //need the StandardOutput contents

I know that the output from the process I am starting is around 7MB long. Running it in the Windows console works fine. Unfortunately programmatically this hangs indefinitely at WaitForExit. Note also this code does NOT hang for smaller outputs (like 3KB).

Is it possible that the internal StandardOutput in ProcessStartInfo can't buffer 7MB? If so, what should I do instead? If not, what am I doing wrong?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(23

灼疼热情 2024-07-13 18:01:52

问题是,如果您重定向 StandardOutput 和/或 StandardError,内部缓冲区可能会变满。 无论您使用什么顺序,都可能会出现问题:

  • 如果您等待进程退出后再读取 StandardOutput,则进程可能会阻止尝试写入它,因此进程永远不会结束。
  • 如果您使用 ReadToEnd 从 StandardOutput 读取数据,那么如果进程永远不会关闭 StandardOutput(例如,如果它从未终止,或者如果它写入 StandardError 时被阻止)。

解决方案是使用异步读取来确保缓冲区不会变满。 为了避免任何死锁并收集 StandardOutputStandardError 的所有输出,您可以执行以下操作:

编辑:请参阅下面的答案,了解如何避免 ObjectDisposeException如果发生超时。

using (Process process = new Process())
{
    process.StartInfo.FileName = filename;
    process.StartInfo.Arguments = arguments;
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.RedirectStandardOutput = true;
    process.StartInfo.RedirectStandardError = true;

    StringBuilder output = new StringBuilder();
    StringBuilder error = new StringBuilder();

    using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
    using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
    {
        process.OutputDataReceived += (sender, e) => {
            if (e.Data == null)
            {
                outputWaitHandle.Set();
            }
            else
            {
                output.AppendLine(e.Data);
            }
        };
        process.ErrorDataReceived += (sender, e) =>
        {
            if (e.Data == null)
            {
                errorWaitHandle.Set();
            }
            else
            {
                error.AppendLine(e.Data);
            }
        };

        process.Start();

        process.BeginOutputReadLine();
        process.BeginErrorReadLine();

        if (process.WaitForExit(timeout) &&
            outputWaitHandle.WaitOne(timeout) &&
            errorWaitHandle.WaitOne(timeout))
        {
            // Process completed. Check process.ExitCode here.
        }
        else
        {
            // Timed out.
        }
    }
}

The problem is that if you redirect StandardOutput and/or StandardError the internal buffer can become full. Whatever order you use, there can be a problem:

  • If you wait for the process to exit before reading StandardOutput the process can block trying to write to it, so the process never ends.
  • If you read from StandardOutput using ReadToEnd then your process can block if the process never closes StandardOutput (for example if it never terminates, or if it is blocked writing to StandardError).

The solution is to use asynchronous reads to ensure that the buffer doesn't get full. To avoid any deadlocks and collect up all output from both StandardOutput and StandardError you can do this:

EDIT: See answers below for how avoid an ObjectDisposedException if the timeout occurs.

using (Process process = new Process())
{
    process.StartInfo.FileName = filename;
    process.StartInfo.Arguments = arguments;
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.RedirectStandardOutput = true;
    process.StartInfo.RedirectStandardError = true;

    StringBuilder output = new StringBuilder();
    StringBuilder error = new StringBuilder();

    using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
    using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
    {
        process.OutputDataReceived += (sender, e) => {
            if (e.Data == null)
            {
                outputWaitHandle.Set();
            }
            else
            {
                output.AppendLine(e.Data);
            }
        };
        process.ErrorDataReceived += (sender, e) =>
        {
            if (e.Data == null)
            {
                errorWaitHandle.Set();
            }
            else
            {
                error.AppendLine(e.Data);
            }
        };

        process.Start();

        process.BeginOutputReadLine();
        process.BeginErrorReadLine();

        if (process.WaitForExit(timeout) &&
            outputWaitHandle.WaitOne(timeout) &&
            errorWaitHandle.WaitOne(timeout))
        {
            // Process completed. Check process.ExitCode here.
        }
        else
        {
            // Timed out.
        }
    }
}
转身泪倾城 2024-07-13 18:01:52

Process.StandardOutput 的文档 表示在等待之前先阅读,否则可能会死锁,复制的代码片段如下:

 // Start the child process.
 Process p = new Process();
 // Redirect the output stream of the child process.
 p.StartInfo.UseShellExecute = false;
 p.StartInfo.RedirectStandardOutput = true;
 p.StartInfo.FileName = "Write500Lines.exe";
 p.Start();
 // Do not wait for the child process to exit before
 // reading to the end of its redirected stream.
 // p.WaitForExit();
 // Read the output stream first and then wait.
 string output = p.StandardOutput.ReadToEnd();
 p.WaitForExit();

The documentation for Process.StandardOutput says to read before you wait otherwise you can deadlock, snippet copied below:

 // Start the child process.
 Process p = new Process();
 // Redirect the output stream of the child process.
 p.StartInfo.UseShellExecute = false;
 p.StartInfo.RedirectStandardOutput = true;
 p.StartInfo.FileName = "Write500Lines.exe";
 p.Start();
 // Do not wait for the child process to exit before
 // reading to the end of its redirected stream.
 // p.WaitForExit();
 // Read the output stream first and then wait.
 string output = p.StandardOutput.ReadToEnd();
 p.WaitForExit();
公布 2024-07-13 18:01:52

这是一个更现代的、基于任务并行库 (TPL) 的解决方案,适用于 .NET 4.5 及更高版本。

使用示例

try
{
    var exitCode = await StartProcess(
        "dotnet", 
        "--version", 
        @"C:\",
        10000, 
        Console.Out, 
        Console.Out);
    Console.WriteLine($"Process Exited with Exit Code {exitCode}!");
}
catch (TaskCanceledException)
{
    Console.WriteLine("Process Timed Out!");
}

实现

public static async Task<int> StartProcess(
    string filename,
    string arguments,
    string workingDirectory= null,
    int? timeout = null,
    TextWriter outputTextWriter = null,
    TextWriter errorTextWriter = null)
{
    using (var process = new Process()
    {
        StartInfo = new ProcessStartInfo()
        {
            CreateNoWindow = true,
            Arguments = arguments,
            FileName = filename,
            RedirectStandardOutput = outputTextWriter != null,
            RedirectStandardError = errorTextWriter != null,
            UseShellExecute = false,
            WorkingDirectory = workingDirectory
        }
    })
    {
        var cancellationTokenSource = timeout.HasValue ?
            new CancellationTokenSource(timeout.Value) :
            new CancellationTokenSource();

        process.Start();

        var tasks = new List<Task>(3) { process.WaitForExitAsync(cancellationTokenSource.Token) };
        if (outputTextWriter != null)
        {
            tasks.Add(ReadAsync(
                x =>
                {
                    process.OutputDataReceived += x;
                    process.BeginOutputReadLine();
                },
                x => process.OutputDataReceived -= x,
                outputTextWriter,
                cancellationTokenSource.Token));
        }

        if (errorTextWriter != null)
        {
            tasks.Add(ReadAsync(
                x =>
                {
                    process.ErrorDataReceived += x;
                    process.BeginErrorReadLine();
                },
                x => process.ErrorDataReceived -= x,
                errorTextWriter,
                cancellationTokenSource.Token));
        }

        await Task.WhenAll(tasks);
        return process.ExitCode;
    }
}

/// <summary>
/// Waits asynchronously for the process to exit.
/// </summary>
/// <param name="process">The process to wait for cancellation.</param>
/// <param name="cancellationToken">A cancellation token. If invoked, the task will return
/// immediately as cancelled.</param>
/// <returns>A Task representing waiting for the process to end.</returns>
public static Task WaitForExitAsync(
    this Process process,
    CancellationToken cancellationToken = default(CancellationToken))
{
    process.EnableRaisingEvents = true;

    var taskCompletionSource = new TaskCompletionSource<object>();

    EventHandler handler = null;
    handler = (sender, args) =>
    {
        process.Exited -= handler;
        taskCompletionSource.TrySetResult(null);
    };
    process.Exited += handler;

    if (cancellationToken != default(CancellationToken))
    {
        cancellationToken.Register(
            () =>
            {
                process.Exited -= handler;
                taskCompletionSource.TrySetCanceled();
            });
    }

    return taskCompletionSource.Task;
}

/// <summary>
/// Reads the data from the specified data recieved event and writes it to the
/// <paramref name="textWriter"/>.
/// </summary>
/// <param name="addHandler">Adds the event handler.</param>
/// <param name="removeHandler">Removes the event handler.</param>
/// <param name="textWriter">The text writer.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public static Task ReadAsync(
    this Action<DataReceivedEventHandler> addHandler,
    Action<DataReceivedEventHandler> removeHandler,
    TextWriter textWriter,
    CancellationToken cancellationToken = default(CancellationToken))
{
    var taskCompletionSource = new TaskCompletionSource<object>();

    DataReceivedEventHandler handler = null;
    handler = new DataReceivedEventHandler(
        (sender, e) =>
        {
            if (e.Data == null)
            {
                removeHandler(handler);
                taskCompletionSource.TrySetResult(null);
            }
            else
            {
                textWriter.WriteLine(e.Data);
            }
        });

    addHandler(handler);

    if (cancellationToken != default(CancellationToken))
    {
        cancellationToken.Register(
            () =>
            {
                removeHandler(handler);
                taskCompletionSource.TrySetCanceled();
            });
    }

    return taskCompletionSource.Task;
}

This is a more modern awaitable, Task Parallel Library (TPL) based solution for .NET 4.5 and above.

Usage Example

try
{
    var exitCode = await StartProcess(
        "dotnet", 
        "--version", 
        @"C:\",
        10000, 
        Console.Out, 
        Console.Out);
    Console.WriteLine($"Process Exited with Exit Code {exitCode}!");
}
catch (TaskCanceledException)
{
    Console.WriteLine("Process Timed Out!");
}

Implementation

public static async Task<int> StartProcess(
    string filename,
    string arguments,
    string workingDirectory= null,
    int? timeout = null,
    TextWriter outputTextWriter = null,
    TextWriter errorTextWriter = null)
{
    using (var process = new Process()
    {
        StartInfo = new ProcessStartInfo()
        {
            CreateNoWindow = true,
            Arguments = arguments,
            FileName = filename,
            RedirectStandardOutput = outputTextWriter != null,
            RedirectStandardError = errorTextWriter != null,
            UseShellExecute = false,
            WorkingDirectory = workingDirectory
        }
    })
    {
        var cancellationTokenSource = timeout.HasValue ?
            new CancellationTokenSource(timeout.Value) :
            new CancellationTokenSource();

        process.Start();

        var tasks = new List<Task>(3) { process.WaitForExitAsync(cancellationTokenSource.Token) };
        if (outputTextWriter != null)
        {
            tasks.Add(ReadAsync(
                x =>
                {
                    process.OutputDataReceived += x;
                    process.BeginOutputReadLine();
                },
                x => process.OutputDataReceived -= x,
                outputTextWriter,
                cancellationTokenSource.Token));
        }

        if (errorTextWriter != null)
        {
            tasks.Add(ReadAsync(
                x =>
                {
                    process.ErrorDataReceived += x;
                    process.BeginErrorReadLine();
                },
                x => process.ErrorDataReceived -= x,
                errorTextWriter,
                cancellationTokenSource.Token));
        }

        await Task.WhenAll(tasks);
        return process.ExitCode;
    }
}

/// <summary>
/// Waits asynchronously for the process to exit.
/// </summary>
/// <param name="process">The process to wait for cancellation.</param>
/// <param name="cancellationToken">A cancellation token. If invoked, the task will return
/// immediately as cancelled.</param>
/// <returns>A Task representing waiting for the process to end.</returns>
public static Task WaitForExitAsync(
    this Process process,
    CancellationToken cancellationToken = default(CancellationToken))
{
    process.EnableRaisingEvents = true;

    var taskCompletionSource = new TaskCompletionSource<object>();

    EventHandler handler = null;
    handler = (sender, args) =>
    {
        process.Exited -= handler;
        taskCompletionSource.TrySetResult(null);
    };
    process.Exited += handler;

    if (cancellationToken != default(CancellationToken))
    {
        cancellationToken.Register(
            () =>
            {
                process.Exited -= handler;
                taskCompletionSource.TrySetCanceled();
            });
    }

    return taskCompletionSource.Task;
}

/// <summary>
/// Reads the data from the specified data recieved event and writes it to the
/// <paramref name="textWriter"/>.
/// </summary>
/// <param name="addHandler">Adds the event handler.</param>
/// <param name="removeHandler">Removes the event handler.</param>
/// <param name="textWriter">The text writer.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public static Task ReadAsync(
    this Action<DataReceivedEventHandler> addHandler,
    Action<DataReceivedEventHandler> removeHandler,
    TextWriter textWriter,
    CancellationToken cancellationToken = default(CancellationToken))
{
    var taskCompletionSource = new TaskCompletionSource<object>();

    DataReceivedEventHandler handler = null;
    handler = new DataReceivedEventHandler(
        (sender, e) =>
        {
            if (e.Data == null)
            {
                removeHandler(handler);
                taskCompletionSource.TrySetResult(null);
            }
            else
            {
                textWriter.WriteLine(e.Data);
            }
        });

    addHandler(handler);

    if (cancellationToken != default(CancellationToken))
    {
        cancellationToken.Register(
            () =>
            {
                removeHandler(handler);
                taskCompletionSource.TrySetCanceled();
            });
    }

    return taskCompletionSource.Task;
}
雨巷深深 2024-07-13 18:01:52

Mark Byers 的回答非常好,但我只想添加以下内容:

OutputDataReceivedErrorDataReceived 委托需要在 outputWaitHandle 和 < code>errorWaitHandle 被处置。 如果进程在超过超时后继续输出数据然后终止,则 outputWaitHandleerrorWaitHandle 变量将在释放后被访问。

(仅供参考,我必须添加此警告作为答案,因为我无法对他的帖子发表评论。)

Mark Byers' answer is excellent, but I would just add the following:

The OutputDataReceived and ErrorDataReceived delegates need to be removed before the outputWaitHandle and errorWaitHandle get disposed. If the process continues to output data after the timeout has been exceeded and then terminates, the outputWaitHandle and errorWaitHandle variables will be accessed after being disposed.

(FYI I had to add this caveat as an answer as I couldn't comment on his post.)

魔法少女 2024-07-13 18:01:52

当进程超时时,会发生未处理的 ObjectDisposeException 的问题。 在这种情况下,条件的其他部分:

if (process.WaitForExit(timeout) 
    && outputWaitHandle.WaitOne(timeout) 
    && errorWaitHandle.WaitOne(timeout))

不会被执行。 我通过以下方式解决了这个问题:

using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
{
    using (Process process = new Process())
    {
        // preparing ProcessStartInfo

        try
        {
            process.OutputDataReceived += (sender, e) =>
                {
                    if (e.Data == null)
                    {
                        outputWaitHandle.Set();
                    }
                    else
                    {
                        outputBuilder.AppendLine(e.Data);
                    }
                };
            process.ErrorDataReceived += (sender, e) =>
                {
                    if (e.Data == null)
                    {
                        errorWaitHandle.Set();
                    }
                    else
                    {
                        errorBuilder.AppendLine(e.Data);
                    }
                };

            process.Start();

            process.BeginOutputReadLine();
            process.BeginErrorReadLine();

            if (process.WaitForExit(timeout))
            {
                exitCode = process.ExitCode;
            }
            else
            {
                // timed out
            }

            output = outputBuilder.ToString();
        }
        finally
        {
            outputWaitHandle.WaitOne(timeout);
            errorWaitHandle.WaitOne(timeout);
        }
    }
}

The problem with unhandled ObjectDisposedException happens when the process is timed out. In such case the other parts of the condition:

if (process.WaitForExit(timeout) 
    && outputWaitHandle.WaitOne(timeout) 
    && errorWaitHandle.WaitOne(timeout))

are not executed. I resolved this problem in a following way:

using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
{
    using (Process process = new Process())
    {
        // preparing ProcessStartInfo

        try
        {
            process.OutputDataReceived += (sender, e) =>
                {
                    if (e.Data == null)
                    {
                        outputWaitHandle.Set();
                    }
                    else
                    {
                        outputBuilder.AppendLine(e.Data);
                    }
                };
            process.ErrorDataReceived += (sender, e) =>
                {
                    if (e.Data == null)
                    {
                        errorWaitHandle.Set();
                    }
                    else
                    {
                        errorBuilder.AppendLine(e.Data);
                    }
                };

            process.Start();

            process.BeginOutputReadLine();
            process.BeginErrorReadLine();

            if (process.WaitForExit(timeout))
            {
                exitCode = process.ExitCode;
            }
            else
            {
                // timed out
            }

            output = outputBuilder.ToString();
        }
        finally
        {
            outputWaitHandle.WaitOne(timeout);
            errorWaitHandle.WaitOne(timeout);
        }
    }
}
最舍不得你 2024-07-13 18:01:52

罗布回答了我的问题,让我节省了几个小时的时间。 等待之前读取输出/错误缓冲区:

// Read the output stream first and then wait.
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();

Rob answered it and saved me few more hours of trials. Read the output/error buffer before waiting:

// Read the output stream first and then wait.
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
锦爱 2024-07-13 18:01:52

我们也有这个问题(或变体)。

请尝试以下操作:

1) 为 p.WaitForExit(nnnn) 添加超时; 其中 nnnn 的单位是毫秒。

2) 将 ReadToEnd 调用放在 WaitForExit 调用之前。 这是我们所看到的 MS 推荐的内容。

We have this issue as well (or a variant).

Try the following:

1) Add a timeout to p.WaitForExit(nnnn); where nnnn is in milliseconds.

2) Put the ReadToEnd call before the WaitForExit call. This is what we've seen MS recommend.

静谧 2024-07-13 18:01:52

归功于 EM0 https://stackoverflow.com/a/17600012/4151626

由于内部超时以及生成的应用程序同时使用 StandardOutput 和 StandardError,我的应用程序的其他解决方案(包括 EM0)仍然陷入僵局。 这对我有用:

Process p = new Process()
{
  StartInfo = new ProcessStartInfo()
  {
    FileName = exe,
    Arguments = args,
    UseShellExecute = false,
    RedirectStandardOutput = true,
    RedirectStandardError = true
  }
};
p.Start();

string cv_error = null;
Thread et = new Thread(() => { cv_error = p.StandardError.ReadToEnd(); });
et.Start();

string cv_out = null;
Thread ot = new Thread(() => { cv_out = p.StandardOutput.ReadToEnd(); });
ot.Start();

p.WaitForExit();
ot.Join();
et.Join();

编辑:将 StartInfo 的初始化添加到代码示例中

Credit to EM0 for https://stackoverflow.com/a/17600012/4151626

The other solutions (including EM0's) still deadlocked for my application, due to internal timeouts and the use of both StandardOutput and StandardError by the spawned application. Here is what worked for me:

Process p = new Process()
{
  StartInfo = new ProcessStartInfo()
  {
    FileName = exe,
    Arguments = args,
    UseShellExecute = false,
    RedirectStandardOutput = true,
    RedirectStandardError = true
  }
};
p.Start();

string cv_error = null;
Thread et = new Thread(() => { cv_error = p.StandardError.ReadToEnd(); });
et.Start();

string cv_out = null;
Thread ot = new Thread(() => { cv_out = p.StandardOutput.ReadToEnd(); });
ot.Start();

p.WaitForExit();
ot.Join();
et.Join();

Edit: added initialization of StartInfo to code sample

墟烟 2024-07-13 18:01:52

我这样解决了这个问题:

            Process proc = new Process();
            proc.StartInfo.FileName = batchFile;
            proc.StartInfo.UseShellExecute = false;
            proc.StartInfo.CreateNoWindow = true;
            proc.StartInfo.RedirectStandardError = true;
            proc.StartInfo.RedirectStandardInput = true;
            proc.StartInfo.RedirectStandardOutput = true;
            proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;      
            proc.Start();
            StreamWriter streamWriter = proc.StandardInput;
            StreamReader outputReader = proc.StandardOutput;
            StreamReader errorReader = proc.StandardError;
            while (!outputReader.EndOfStream)
            {
                string text = outputReader.ReadLine();                    
                streamWriter.WriteLine(text);
            }

            while (!errorReader.EndOfStream)
            {                   
                string text = errorReader.ReadLine();
                streamWriter.WriteLine(text);
            }

            streamWriter.Close();
            proc.WaitForExit();

我重定向了输入、输出和错误,并处理了从输出和错误流中的读取。
该解决方案适用于 SDK 7-8.1,同时适用于 Windows 7 和 Windows 8

I solved it this way:

            Process proc = new Process();
            proc.StartInfo.FileName = batchFile;
            proc.StartInfo.UseShellExecute = false;
            proc.StartInfo.CreateNoWindow = true;
            proc.StartInfo.RedirectStandardError = true;
            proc.StartInfo.RedirectStandardInput = true;
            proc.StartInfo.RedirectStandardOutput = true;
            proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;      
            proc.Start();
            StreamWriter streamWriter = proc.StandardInput;
            StreamReader outputReader = proc.StandardOutput;
            StreamReader errorReader = proc.StandardError;
            while (!outputReader.EndOfStream)
            {
                string text = outputReader.ReadLine();                    
                streamWriter.WriteLine(text);
            }

            while (!errorReader.EndOfStream)
            {                   
                string text = errorReader.ReadLine();
                streamWriter.WriteLine(text);
            }

            streamWriter.Close();
            proc.WaitForExit();

I redirected both input, output and error and handled reading from output and error streams.
This solution works for SDK 7- 8.1, both for Windows 7 and Windows 8

十二 2024-07-13 18:01:52

我尝试创建一个类,通过考虑 Mark Byers、Rob、stevejay 的答案,使用异步流读取来解决您的问题。 这样做我意识到存在与异步进程输出流读取相关的错误。

我在 Microsoft 报告了该错误: https://connect.microsoft.com/VisualStudio/feedback /details/3119134

摘要:

你不能这样做:

process.BeginOutputReadLine(); 进程.Start();

您将收到 System.InvalidOperationException :StandardOut 有
尚未重定向或该过程尚未开始。

================================================== =================================================== ===========================

然后你必须在进程结束后开始异步输出读取
开始:

进程.Start(); process.BeginOutputReadLine();

这样做会产生竞争条件,因为输出流可以接收
将其设置为异步之前的数据:

process.Start(); 
// Here the operating system could give the cpu to another thread.  
// For example, the newly created thread (Process) and it could start writing to the output
// immediately before next line would execute. 
// That create a race condition.
process.BeginOutputReadLine();

================================================== =================================================== ===========================

那么有些人可能会说你只需要阅读流即可
在将其设置为异步之前。 但同样的问题也出现了。 那里
将成为同步读取和设置之间的竞争条件
流式传输到异步模式。

================================================== =================================================== ===========================

无法实现输出流的安全异步读取
进程的实际方式“Process”和“ProcessStartInfo”具有
已设计。

您可能最好使用异步读取,就像其他用户针对您的情况所建议的那样。 但您应该意识到,由于竞争条件,您可能会错过一些信息。

I tried to make a class that would solve your problem using asynchronous stream read, by taking in account Mark Byers, Rob, stevejay answers. Doing so I realised that there is a bug related to asynchronous process output stream read.

I reported that bug at Microsoft: https://connect.microsoft.com/VisualStudio/feedback/details/3119134

Summary:

You can't do that:

process.BeginOutputReadLine(); process.Start();

You will receive System.InvalidOperationException : StandardOut has
not been redirected or the process hasn't started yet.

============================================================================================================================

Then you have to start asynchronous output read after the process is
started:

process.Start(); process.BeginOutputReadLine();

Doing so, make a race condition because the output stream can receive
data before you set it to asynchronous:

process.Start(); 
// Here the operating system could give the cpu to another thread.  
// For example, the newly created thread (Process) and it could start writing to the output
// immediately before next line would execute. 
// That create a race condition.
process.BeginOutputReadLine();

============================================================================================================================

Then some people could say that you just have to read the stream
before you set it to asynchronous. But the same problem occurs. There
will be a race condition between the synchronous read and set the
stream into asynchronous mode.

============================================================================================================================

There is no way to acheive safe asynchronous read of an output stream
of a process in the actual way "Process" and "ProcessStartInfo" has
been designed.

You are probably better using asynchronous read like suggested by other users for your case. But you should be aware that you could miss some information due to race condition.

静谧 2024-07-13 18:01:52

我认为使用异步,可以有一个更优雅的解决方案,即使同时使用 standardOutput 和 standardError 也不会出现死锁:

using (Process process = new Process())
{
    process.StartInfo.FileName = filename;
    process.StartInfo.Arguments = arguments;
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.RedirectStandardOutput = true;
    process.StartInfo.RedirectStandardError = true;

    process.Start();

    var tStandardOutput = process.StandardOutput.ReadToEndAsync();
    var tStandardError = process.StandardError.ReadToEndAsync();

    if (process.WaitForExit(timeout))
    {
        string output = await tStandardOutput;
        string errors = await tStandardError;

        // Process completed. Check process.ExitCode here.
    }
    else
    {
        // Timed out.
    }
}

它基于 Mark Byers 的答案。
如果您不在异步方法中,则可以使用 string output = tStandardOutput.result; 而不是 await

I think with async, it is possible to have a more elegant solution and not having deadlocks even when using both standardOutput and standardError:

using (Process process = new Process())
{
    process.StartInfo.FileName = filename;
    process.StartInfo.Arguments = arguments;
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.RedirectStandardOutput = true;
    process.StartInfo.RedirectStandardError = true;

    process.Start();

    var tStandardOutput = process.StandardOutput.ReadToEndAsync();
    var tStandardError = process.StandardError.ReadToEndAsync();

    if (process.WaitForExit(timeout))
    {
        string output = await tStandardOutput;
        string errors = await tStandardError;

        // Process completed. Check process.ExitCode here.
    }
    else
    {
        // Timed out.
    }
}

It is base on Mark Byers answer.
If you are not in an async method, you can use string output = tStandardOutput.result; instead of await

朱染 2024-07-13 18:01:52

我读过很多答案并做出了自己的答案。 不确定这是否会在任何情况下修复,但它在我的环境中修复。 我只是不使用 WaitForExit 并在输出和输出上使用 WaitHandle.WaitAll 错误结束信号。 如果有人会看到这方面可能存在的问题,我会很高兴。 或者如果它会对某人有帮助。 对我来说更好,因为不使用超时。

private static int DoProcess(string workingDir, string fileName, string arguments)
{
    int exitCode;
    using (var process = new Process
    {
        StartInfo =
        {
            WorkingDirectory = workingDir,
            WindowStyle = ProcessWindowStyle.Hidden,
            CreateNoWindow = true,
            UseShellExecute = false,
            FileName = fileName,
            Arguments = arguments,
            RedirectStandardError = true,
            RedirectStandardOutput = true
        },
        EnableRaisingEvents = true
    })
    {
        using (var outputWaitHandle = new AutoResetEvent(false))
        using (var errorWaitHandle = new AutoResetEvent(false))
        {
            process.OutputDataReceived += (sender, args) =>
            {
                // ReSharper disable once AccessToDisposedClosure
                if (args.Data != null) Debug.Log(args.Data);
                else outputWaitHandle.Set();
            };
            process.ErrorDataReceived += (sender, args) =>
            {
                // ReSharper disable once AccessToDisposedClosure
                if (args.Data != null) Debug.LogError(args.Data);
                else errorWaitHandle.Set();
            };

            process.Start();
            process.BeginOutputReadLine();
            process.BeginErrorReadLine();

            WaitHandle.WaitAll(new WaitHandle[] { outputWaitHandle, errorWaitHandle });

            exitCode = process.ExitCode;
        }
    }
    return exitCode;
}

I've read many of the answers and made my own. Not sure this one will fix in any case, but it fixes in my environment. I'm just not using WaitForExit and use WaitHandle.WaitAll on both output & error end signals. I will be glad, if someone will see possible problems with that. Or if it will help someone. For me it's better because not uses timeouts.

private static int DoProcess(string workingDir, string fileName, string arguments)
{
    int exitCode;
    using (var process = new Process
    {
        StartInfo =
        {
            WorkingDirectory = workingDir,
            WindowStyle = ProcessWindowStyle.Hidden,
            CreateNoWindow = true,
            UseShellExecute = false,
            FileName = fileName,
            Arguments = arguments,
            RedirectStandardError = true,
            RedirectStandardOutput = true
        },
        EnableRaisingEvents = true
    })
    {
        using (var outputWaitHandle = new AutoResetEvent(false))
        using (var errorWaitHandle = new AutoResetEvent(false))
        {
            process.OutputDataReceived += (sender, args) =>
            {
                // ReSharper disable once AccessToDisposedClosure
                if (args.Data != null) Debug.Log(args.Data);
                else outputWaitHandle.Set();
            };
            process.ErrorDataReceived += (sender, args) =>
            {
                // ReSharper disable once AccessToDisposedClosure
                if (args.Data != null) Debug.LogError(args.Data);
                else errorWaitHandle.Set();
            };

            process.Start();
            process.BeginOutputReadLine();
            process.BeginErrorReadLine();

            WaitHandle.WaitAll(new WaitHandle[] { outputWaitHandle, errorWaitHandle });

            exitCode = process.ExitCode;
        }
    }
    return exitCode;
}
心清如水 2024-07-13 18:01:52

我认为这是简单且更好的方法(我们不需要 AutoResetEvent ):

public static string GGSCIShell(string Path, string Command)
{
    using (Process process = new Process())
    {
        process.StartInfo.WorkingDirectory = Path;
        process.StartInfo.FileName = Path + @"\ggsci.exe";
        process.StartInfo.CreateNoWindow = true;
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.RedirectStandardInput = true;
        process.StartInfo.UseShellExecute = false;

        StringBuilder output = new StringBuilder();
        process.OutputDataReceived += (sender, e) =>
        {
            if (e.Data != null)
            {
                output.AppendLine(e.Data);
            }
        };

        process.Start();
        process.StandardInput.WriteLine(Command);
        process.BeginOutputReadLine();


        int timeoutParts = 10;
        int timeoutPart = (int)TIMEOUT / timeoutParts;
        do
        {
            Thread.Sleep(500);//sometimes halv scond is enough to empty output buff (therefore "exit" will be accepted without "timeoutPart" waiting)
            process.StandardInput.WriteLine("exit");
            timeoutParts--;
        }
        while (!process.WaitForExit(timeoutPart) && timeoutParts > 0);

        if (timeoutParts <= 0)
        {
            output.AppendLine("------ GGSCIShell TIMEOUT: " + TIMEOUT + "ms ------");
        }

        string result = output.ToString();
        return result;
    }
}

I thing that this is simple and better approach (we don't need AutoResetEvent):

public static string GGSCIShell(string Path, string Command)
{
    using (Process process = new Process())
    {
        process.StartInfo.WorkingDirectory = Path;
        process.StartInfo.FileName = Path + @"\ggsci.exe";
        process.StartInfo.CreateNoWindow = true;
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.RedirectStandardInput = true;
        process.StartInfo.UseShellExecute = false;

        StringBuilder output = new StringBuilder();
        process.OutputDataReceived += (sender, e) =>
        {
            if (e.Data != null)
            {
                output.AppendLine(e.Data);
            }
        };

        process.Start();
        process.StandardInput.WriteLine(Command);
        process.BeginOutputReadLine();


        int timeoutParts = 10;
        int timeoutPart = (int)TIMEOUT / timeoutParts;
        do
        {
            Thread.Sleep(500);//sometimes halv scond is enough to empty output buff (therefore "exit" will be accepted without "timeoutPart" waiting)
            process.StandardInput.WriteLine("exit");
            timeoutParts--;
        }
        while (!process.WaitForExit(timeoutPart) && timeoutParts > 0);

        if (timeoutParts <= 0)
        {
            output.AppendLine("------ GGSCIShell TIMEOUT: " + TIMEOUT + "ms ------");
        }

        string result = output.ToString();
        return result;
    }
}
童话里做英雄 2024-07-13 18:01:52

上面的答案都不起作用。

Rob 解决方案挂起,“Mark Byers”解决方案获得已处理的异常。(我尝试了其他答案的“解决方案”)。

所以我决定提出另一个解决方案:

public void GetProcessOutputWithTimeout(Process process, int timeoutSec, CancellationToken token, out string output, out int exitCode)
{
    string outputLocal = "";  int localExitCode = -1;
    var task = System.Threading.Tasks.Task.Factory.StartNew(() =>
    {
        outputLocal = process.StandardOutput.ReadToEnd();
        process.WaitForExit();
        localExitCode = process.ExitCode;
    }, token);

    if (task.Wait(timeoutSec, token))
    {
        output = outputLocal;
        exitCode = localExitCode;
    }
    else
    {
        exitCode = -1;
        output = "";
    }
}

using (var process = new Process())
{
    process.StartInfo = ...;
    process.Start();
    string outputUnicode; int exitCode;
    GetProcessOutputWithTimeout(process, PROCESS_TIMEOUT, out outputUnicode, out exitCode);
}

这段代码经过调试并且工作完美。

None of the answers above is doing the job.

Rob solution hangs and 'Mark Byers' solution get the disposed exception.(I tried the "solutions" of the other answers).

So I decided to suggest another solution:

public void GetProcessOutputWithTimeout(Process process, int timeoutSec, CancellationToken token, out string output, out int exitCode)
{
    string outputLocal = "";  int localExitCode = -1;
    var task = System.Threading.Tasks.Task.Factory.StartNew(() =>
    {
        outputLocal = process.StandardOutput.ReadToEnd();
        process.WaitForExit();
        localExitCode = process.ExitCode;
    }, token);

    if (task.Wait(timeoutSec, token))
    {
        output = outputLocal;
        exitCode = localExitCode;
    }
    else
    {
        exitCode = -1;
        output = "";
    }
}

using (var process = new Process())
{
    process.StartInfo = ...;
    process.Start();
    string outputUnicode; int exitCode;
    GetProcessOutputWithTimeout(process, PROCESS_TIMEOUT, out outputUnicode, out exitCode);
}

This code debugged and works perfectly.

拥醉 2024-07-13 18:01:52

简介

目前接受的答案不起作用(引发异常),并且有太多解决方法,但没有完整的代码。 这显然浪费了很多人的时间,因为这是一个很受欢迎的问题。

结合 Mark Byers 的答案和 Karol Tyl 的答案,我根据我想要如何使用 Process.Start 方法编写了完整的代码。

用法

我用它来创建围绕 git 命令的进度对话框。 这就是我使用它的方式:

    private bool Run(string fullCommand)
    {
        Error = "";
        int timeout = 5000;

        var result = ProcessNoBS.Start(
            filename: @"C:\Program Files\Git\cmd\git.exe",
            arguments: fullCommand,
            timeoutInMs: timeout,
            workingDir: @"C:\test");

        if (result.hasTimedOut)
        {
            Error = String.Format("Timeout ({0} sec)", timeout/1000);
            return false;
        }

        if (result.ExitCode != 0)
        {
            Error = (String.IsNullOrWhiteSpace(result.stderr)) 
                ? result.stdout : result.stderr;
            return false;
        }

        return true;
    }

理论上你也可以结合 stdout 和 stderr,但我还没有测试过。

代码

public struct ProcessResult
{
    public string stdout;
    public string stderr;
    public bool hasTimedOut;
    private int? exitCode;

    public ProcessResult(bool hasTimedOut = true)
    {
        this.hasTimedOut = hasTimedOut;
        stdout = null;
        stderr = null;
        exitCode = null;
    }

    public int ExitCode
    {
        get 
        {
            if (hasTimedOut)
                throw new InvalidOperationException(
                    "There was no exit code - process has timed out.");

            return (int)exitCode;
        }
        set
        {
            exitCode = value;
        }
    }
}

public class ProcessNoBS
{
    public static ProcessResult Start(string filename, string arguments,
        string workingDir = null, int timeoutInMs = 5000,
        bool combineStdoutAndStderr = false)
    {
        using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
        using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
        {
            using (var process = new Process())
            {
                var info = new ProcessStartInfo();

                info.CreateNoWindow = true;
                info.FileName = filename;
                info.Arguments = arguments;
                info.UseShellExecute = false;
                info.RedirectStandardOutput = true;
                info.RedirectStandardError = true;

                if (workingDir != null)
                    info.WorkingDirectory = workingDir;

                process.StartInfo = info;

                StringBuilder stdout = new StringBuilder();
                StringBuilder stderr = combineStdoutAndStderr
                    ? stdout : new StringBuilder();

                var result = new ProcessResult();

                try
                {
                    process.OutputDataReceived += (sender, e) =>
                    {
                        if (e.Data == null)
                            outputWaitHandle.Set();
                        else
                            stdout.AppendLine(e.Data);
                    };
                    process.ErrorDataReceived += (sender, e) =>
                    {
                        if (e.Data == null)
                            errorWaitHandle.Set();
                        else
                            stderr.AppendLine(e.Data);
                    };

                    process.Start();

                    process.BeginOutputReadLine();
                    process.BeginErrorReadLine();

                    if (process.WaitForExit(timeoutInMs))
                        result.ExitCode = process.ExitCode;
                    // else process has timed out 
                    // but that's already default ProcessResult

                    result.stdout = stdout.ToString();
                    if (combineStdoutAndStderr)
                        result.stderr = null;
                    else
                        result.stderr = stderr.ToString();

                    return result;
                }
                finally
                {
                    outputWaitHandle.WaitOne(timeoutInMs);
                    errorWaitHandle.WaitOne(timeoutInMs);
                }
            }
        }
    }
}

Introduction

Currently accepted answer doesn't work (throws exception) and there are too many workarounds but no complete code. This is obviously wasting lots of people's time because this is a popular question.

Combining Mark Byers' answer and Karol Tyl's answer I wrote full code based on how I want to use the Process.Start method.

Usage

I have used it to create progress dialog around git commands. This is how I've used it:

    private bool Run(string fullCommand)
    {
        Error = "";
        int timeout = 5000;

        var result = ProcessNoBS.Start(
            filename: @"C:\Program Files\Git\cmd\git.exe",
            arguments: fullCommand,
            timeoutInMs: timeout,
            workingDir: @"C:\test");

        if (result.hasTimedOut)
        {
            Error = String.Format("Timeout ({0} sec)", timeout/1000);
            return false;
        }

        if (result.ExitCode != 0)
        {
            Error = (String.IsNullOrWhiteSpace(result.stderr)) 
                ? result.stdout : result.stderr;
            return false;
        }

        return true;
    }

In theory you can also combine stdout and stderr, but I haven't tested that.

Code

public struct ProcessResult
{
    public string stdout;
    public string stderr;
    public bool hasTimedOut;
    private int? exitCode;

    public ProcessResult(bool hasTimedOut = true)
    {
        this.hasTimedOut = hasTimedOut;
        stdout = null;
        stderr = null;
        exitCode = null;
    }

    public int ExitCode
    {
        get 
        {
            if (hasTimedOut)
                throw new InvalidOperationException(
                    "There was no exit code - process has timed out.");

            return (int)exitCode;
        }
        set
        {
            exitCode = value;
        }
    }
}

public class ProcessNoBS
{
    public static ProcessResult Start(string filename, string arguments,
        string workingDir = null, int timeoutInMs = 5000,
        bool combineStdoutAndStderr = false)
    {
        using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
        using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
        {
            using (var process = new Process())
            {
                var info = new ProcessStartInfo();

                info.CreateNoWindow = true;
                info.FileName = filename;
                info.Arguments = arguments;
                info.UseShellExecute = false;
                info.RedirectStandardOutput = true;
                info.RedirectStandardError = true;

                if (workingDir != null)
                    info.WorkingDirectory = workingDir;

                process.StartInfo = info;

                StringBuilder stdout = new StringBuilder();
                StringBuilder stderr = combineStdoutAndStderr
                    ? stdout : new StringBuilder();

                var result = new ProcessResult();

                try
                {
                    process.OutputDataReceived += (sender, e) =>
                    {
                        if (e.Data == null)
                            outputWaitHandle.Set();
                        else
                            stdout.AppendLine(e.Data);
                    };
                    process.ErrorDataReceived += (sender, e) =>
                    {
                        if (e.Data == null)
                            errorWaitHandle.Set();
                        else
                            stderr.AppendLine(e.Data);
                    };

                    process.Start();

                    process.BeginOutputReadLine();
                    process.BeginErrorReadLine();

                    if (process.WaitForExit(timeoutInMs))
                        result.ExitCode = process.ExitCode;
                    // else process has timed out 
                    // but that's already default ProcessResult

                    result.stdout = stdout.ToString();
                    if (combineStdoutAndStderr)
                        result.stderr = null;
                    else
                        result.stderr = stderr.ToString();

                    return result;
                }
                finally
                {
                    outputWaitHandle.WaitOne(timeoutInMs);
                    errorWaitHandle.WaitOne(timeoutInMs);
                }
            }
        }
    }
}
作业与我同在 2024-07-13 18:01:52

我知道这已经很老了,但是,在阅读了整个页面之后,没有一个解决方案对我有用,尽管我没有尝试 Muhammad Rehan,因为代码有点难以遵循,尽管我猜他走在正确的轨道上。 当我说它不起作用时,这并不完全正确,有时它会很好地工作,我想这与 EOF 标记之前的输出长度有关。

无论如何,对我有用的解决方案是使用不同的线程来读取 StandardOutput 和 StandardError 并写入消息。

        StreamWriter sw = null;
        var queue = new ConcurrentQueue<string>();

        var flushTask = new System.Timers.Timer(50);
        flushTask.Elapsed += (s, e) =>
        {
            while (!queue.IsEmpty)
            {
                string line = null;
                if (queue.TryDequeue(out line))
                    sw.WriteLine(line);
            }
            sw.FlushAsync();
        };
        flushTask.Start();

        using (var process = new Process())
        {
            try
            {
                process.StartInfo.FileName = @"...";
                process.StartInfo.Arguments = $"...";
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.RedirectStandardOutput = true;
                process.StartInfo.RedirectStandardError = true;

                process.Start();

                var outputRead = Task.Run(() =>
                {
                    while (!process.StandardOutput.EndOfStream)
                    {
                        queue.Enqueue(process.StandardOutput.ReadLine());
                    }
                });

                var errorRead = Task.Run(() =>
                {
                    while (!process.StandardError.EndOfStream)
                    {
                        queue.Enqueue(process.StandardError.ReadLine());
                    }
                });

                var timeout = new TimeSpan(hours: 0, minutes: 10, seconds: 0);

                if (Task.WaitAll(new[] { outputRead, errorRead }, timeout) &&
                    process.WaitForExit((int)timeout.TotalMilliseconds))
                {
                    if (process.ExitCode != 0)
                    {
                        throw new Exception($"Failed run... blah blah");
                    }
                }
                else
                {
                    throw new Exception($"process timed out after waiting {timeout}");
                }
            }
            catch (Exception e)
            {
                throw new Exception($"Failed to succesfully run the process.....", e);
            }
        }
    }

希望这可以帮助那些认为这很难的人!

I know that this is supper old but, after reading this whole page none of the solutions was working for me, although I didn't try Muhammad Rehan as the code was a little hard to follow, although I guess he was on the right track. When I say it didn't work that's not entirely true, sometimes it would work fine, I guess it is something to do with the length of the output before an EOF mark.

Anyway, the solution that worked for me was to use different threads to read the StandardOutput and StandardError and write the messages.

        StreamWriter sw = null;
        var queue = new ConcurrentQueue<string>();

        var flushTask = new System.Timers.Timer(50);
        flushTask.Elapsed += (s, e) =>
        {
            while (!queue.IsEmpty)
            {
                string line = null;
                if (queue.TryDequeue(out line))
                    sw.WriteLine(line);
            }
            sw.FlushAsync();
        };
        flushTask.Start();

        using (var process = new Process())
        {
            try
            {
                process.StartInfo.FileName = @"...";
                process.StartInfo.Arguments = $"...";
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.RedirectStandardOutput = true;
                process.StartInfo.RedirectStandardError = true;

                process.Start();

                var outputRead = Task.Run(() =>
                {
                    while (!process.StandardOutput.EndOfStream)
                    {
                        queue.Enqueue(process.StandardOutput.ReadLine());
                    }
                });

                var errorRead = Task.Run(() =>
                {
                    while (!process.StandardError.EndOfStream)
                    {
                        queue.Enqueue(process.StandardError.ReadLine());
                    }
                });

                var timeout = new TimeSpan(hours: 0, minutes: 10, seconds: 0);

                if (Task.WaitAll(new[] { outputRead, errorRead }, timeout) &&
                    process.WaitForExit((int)timeout.TotalMilliseconds))
                {
                    if (process.ExitCode != 0)
                    {
                        throw new Exception($"Failed run... blah blah");
                    }
                }
                else
                {
                    throw new Exception($"process timed out after waiting {timeout}");
                }
            }
            catch (Exception e)
            {
                throw new Exception($"Failed to succesfully run the process.....", e);
            }
        }
    }

Hope this helps someone, who thought this could be so hard!

夏天碎花小短裙 2024-07-13 18:01:52

阅读完这里的所有帖子后,我选择了 Marko Avlijaš 的综合解决方案。
但是,它并没有解决我的所有问题。

在我们的环境中,我们有一个 Windows 服务,它计划运行数百个不同的 .bat .cmd .exe 等文件,这些文件是多年来积累的、由许多不同的人以不同的风格编写的。 我们无法控制程序的编写和编写。 脚本,我们只负责调度、运行和报告成功/失败。

所以我尝试了这里几乎所有的建议,并取得了不同程度的成功。 Marko 的答案几乎是完美的,但是当作为服务运行时,它并不总是捕获标准输出。 我从来没有弄清楚为什么不。

我们发现适用于所有情况的唯一解决方案是: http: //csharptest.net/319/using-the-processrunner-class/index.html

After reading all the posts here, i settled on the consolidated solution of Marko Avlijaš.
However, it did not solve all of my issues.

In our environment we have a Windows Service which is scheduled to run hundreds of different .bat .cmd .exe,... etc. files which have accumulated over the years and were written by many different people and in different styles. We have no control over the writing of the programs & scripts, we are just responsible for scheduling, running, and reporting on success/failure.

So i tried pretty much all of the suggestions here with different levels of success. Marko's answer was almost perfect, but when run as a service, it didnt always capture stdout. I never got to the bottom of why not.

The only solution we found that works in ALL our cases is this : http://csharptest.net/319/using-the-processrunner-class/index.html

给不了的爱 2024-07-13 18:01:52

我最终使用的解决方法是为了避免所有复杂性:

var outputFile = Path.GetTempFileName();
info = new System.Diagnostics.ProcessStartInfo("TheProgram.exe", String.Join(" ", args) + " > " + outputFile + " 2>&1");
info.CreateNoWindow = true;
info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
info.UseShellExecute = false;
System.Diagnostics.Process p = System.Diagnostics.Process.Start(info);
p.WaitForExit();
Console.WriteLine(File.ReadAllText(outputFile)); //need the StandardOutput contents

因此我创建了一个临时文件,通过使用 > 将输出和错误重定向到它。 输出文件> 2>&1 然后在进程完成后读取文件。

其他解决方案适用于您想要对输出执行其他操作的场景,但对于简单的操作,这可以避免很多复杂性。

Workaround I ended up using to avoid all the complexity:

var outputFile = Path.GetTempFileName();
info = new System.Diagnostics.ProcessStartInfo("TheProgram.exe", String.Join(" ", args) + " > " + outputFile + " 2>&1");
info.CreateNoWindow = true;
info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
info.UseShellExecute = false;
System.Diagnostics.Process p = System.Diagnostics.Process.Start(info);
p.WaitForExit();
Console.WriteLine(File.ReadAllText(outputFile)); //need the StandardOutput contents

So I create a temp file, redirect both the output and error to it by using > outputfile > 2>&1 and then just read the file after the process has finished.

The other solutions are fine for scenarios where you want to do other stuff with the output, but for simple stuff this avoids a lot of complexity.

裸钻 2024-07-13 18:01:52

就我而言,我遇到了错误,所以我只是徒劳地等待正常的输出。

我将顺序从: 改为

string result = process.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();

string error = process.StandardError.ReadToEnd();

if (string.IsNullOrEmpty(error))
    string result = process.StandardOutput.ReadToEnd();

In my case I had an error so I just waited in vain for a normal ouput.

I switched the order from this:

string result = process.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();

To this:

string error = process.StandardError.ReadToEnd();

if (string.IsNullOrEmpty(error))
    string result = process.StandardOutput.ReadToEnd();
夜光 2024-07-13 18:01:52

这篇文章可能已经过时,但我发现它通常挂起的主要原因是由于重定向标准输出的堆栈溢出或者如果您有重定向标准错误。

由于输出数据或错误数据很大,因此会导致挂起时间,因为它仍在无限期地处理。

所以要解决这个问题:

p.StartInfo.RedirectStandardoutput = False
p.StartInfo.RedirectStandarderror = False

This post maybe outdated but i found out the main cause why it usually hang is due to stack overflow for the redirectStandardoutput or if you have redirectStandarderror.

As the output data or the error data is large, it will cause a hang time as it is still processing for indefinite duration.

so to resolve this issue:

p.StartInfo.RedirectStandardoutput = False
p.StartInfo.RedirectStandarderror = False
蓬勃野心 2024-07-13 18:01:52

让我们将此处发布的示例代码称为重定向程序,将另一个程序称为重定向程序。 如果是我,那么我可能会编写一个可用于重复问题的测试重定向程序。

所以我做了。 对于测试数据,我使用了 ECMA-334 C# 语言规范 v PDF; 大约是5MB。 以下是其中的重要部分。

StreamReader stream = null;
try { stream = new StreamReader(Path); }
catch (Exception ex)
{
    Console.Error.WriteLine("Input open error: " + ex.Message);
    return;
}
Console.SetIn(stream);
int datasize = 0;
try
{
    string record = Console.ReadLine();
    while (record != null)
    {
        datasize += record.Length + 2;
        record = Console.ReadLine();
        Console.WriteLine(record);
    }
}
catch (Exception ex)
{
    Console.Error.WriteLine($"Error: {ex.Message}");
    return;
}

数据大小值与实际文件大小不匹配,但这并不重要。 目前尚不清楚 PDF 文件是否总是在行尾同时使用 CR 和 LF,但这并不重要。 您可以使用任何其他大型文本文件进行测试。

使用该示例重定向器代码在写入大量数据时会挂起,但在写入少量数据时不会挂起。

我非常尝试以某种方式跟踪该代码的执行,但我做不到。 我注释掉了重定向程序中禁止为重定向程序创建控制台的行,以尝试获取单独的控制台窗口,但我不能。

然后我发现 如何在新窗口、父窗口或无窗口中启动控制台应用。 因此,显然,当一个控制台程序在没有 ShellExecute 的情况下启动另一个控制台程序时,我们不能(轻松)拥有一个单独的控制台,并且由于 ShellExecute 不支持重定向,我们必须共享一个控制台,即使我们没有为另一个进程指定窗口。

我假设,如果重定向程序在某处填满了缓冲区,那么它必须等待读取数据,如果此时重定向器没有读取数据,那么它就是死锁。

解决方案是不使用ReadToEnd,在写入数据时读取数据,但不一定要使用异步读取。 解决方案可能非常简单。 以下内容适用于 5 MB PDF。

ProcessStartInfo info = new ProcessStartInfo(TheProgram);
info.CreateNoWindow = true;
info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
info.RedirectStandardOutput = true;
info.UseShellExecute = false;
Process p = Process.Start(info);
string record = p.StandardOutput.ReadLine();
while (record != null)
{
    Console.WriteLine(record);
    record = p.StandardOutput.ReadLine();
}
p.WaitForExit();

另一种可能性是使用 GUI 程序来进行重定向。 除了明显的修改之外,前面的代码可以在 WPF 应用程序中运行。

Let us call the sample code posted here the redirector and the other program the redirected. If it were me then I would probably write a test redirected program that can be used to duplicate the problem.

So I did. For test data I used the ECMA-334 C# Language Specificationv PDF; it is about 5MB. The following is the important part of that.

StreamReader stream = null;
try { stream = new StreamReader(Path); }
catch (Exception ex)
{
    Console.Error.WriteLine("Input open error: " + ex.Message);
    return;
}
Console.SetIn(stream);
int datasize = 0;
try
{
    string record = Console.ReadLine();
    while (record != null)
    {
        datasize += record.Length + 2;
        record = Console.ReadLine();
        Console.WriteLine(record);
    }
}
catch (Exception ex)
{
    Console.Error.WriteLine($"Error: {ex.Message}");
    return;
}

The datasize value does not match the actual file size but that does not matter. It is not clear if a PDF file always uses both CR and LF at the end of lines but that does not matter for this. You can use any other large text file to test with.

Using that the sample redirector code hangs when I write the large amount of data but not when I write a small amount.

I tried very much to somehow trace the execution of that code and I could not. I commented out the lines of the redirected program that disabled creation of a console for the redirected program to try to get a separate console window but I could not.

Then I found How to start a console app in a new window, the parent’s window, or no window. So apparently we cannot (easily) have a separate console when one console program starts another console program without ShellExecute and since ShellExecute does not support redirection we must share a console, even if we specify no window for the other process.

I assume that if the redirected program fills up a buffer somewhere then it must wait for the data to be read and if at that point no data is read by the redirector then it is a deadlock.

The solution is to not use ReadToEnd and to read the data while the data is being written but it is not necessary to use asynchronous reads. The solution can be quite simple. The following works for me with the 5 MB PDF.

ProcessStartInfo info = new ProcessStartInfo(TheProgram);
info.CreateNoWindow = true;
info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
info.RedirectStandardOutput = true;
info.UseShellExecute = false;
Process p = Process.Start(info);
string record = p.StandardOutput.ReadLine();
while (record != null)
{
    Console.WriteLine(record);
    record = p.StandardOutput.ReadLine();
}
p.WaitForExit();

Another possibility is to use a GUI program to do the redirection. The preceding code works in a WPF application except with obvious modifications.

没有你我更好 2024-07-13 18:01:52

这是我几乎同步的解决方案。 您应该调用 StandardOutput.ReadToEndAsync()StandardError.ReadToEndAsync()
同时在一起!

    public static void EvaluateJavaScript(string script) {
        script = script.Replace( @"""", @"\""" );
        using var process = System.Diagnostics.Process.Start( new System.Diagnostics.ProcessStartInfo() {
            FileName = "node",
            Arguments = $@"--eval ""{script}""",
            UseShellExecute = false,
            CreateNoWindow = true,
            RedirectStandardInput = true,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
        } );
        {
            while (true) {
                var output = process.StandardOutput.ReadToEndAsync();
                var error = process.StandardError.ReadToEndAsync();

                if (!string.IsNullOrEmpty( output.Result )) {
                    Debug.Log( output.Result );
                }
                if (!string.IsNullOrEmpty( error.Result )) {
                    Debug.LogError( error.Result );
                }
                if (string.IsNullOrEmpty( output.Result ) && string.IsNullOrEmpty( error.Result )) {
                    break;
                }
            }

            process.StandardInput.Close();
            process.StandardOutput.Close();
            process.StandardError.Close();
        }
        process.WaitForExit( 10_000 );
    }

This is my almost synchronous solution. You should invoke StandardOutput.ReadToEndAsync() and StandardError.ReadToEndAsync()
together at the same time!

    public static void EvaluateJavaScript(string script) {
        script = script.Replace( @"""", @"\""" );
        using var process = System.Diagnostics.Process.Start( new System.Diagnostics.ProcessStartInfo() {
            FileName = "node",
            Arguments = $@"--eval ""{script}""",
            UseShellExecute = false,
            CreateNoWindow = true,
            RedirectStandardInput = true,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
        } );
        {
            while (true) {
                var output = process.StandardOutput.ReadToEndAsync();
                var error = process.StandardError.ReadToEndAsync();

                if (!string.IsNullOrEmpty( output.Result )) {
                    Debug.Log( output.Result );
                }
                if (!string.IsNullOrEmpty( error.Result )) {
                    Debug.LogError( error.Result );
                }
                if (string.IsNullOrEmpty( output.Result ) && string.IsNullOrEmpty( error.Result )) {
                    break;
                }
            }

            process.StandardInput.Close();
            process.StandardOutput.Close();
            process.StandardError.Close();
        }
        process.WaitForExit( 10_000 );
    }
杯别 2024-07-13 18:01:52

我也遇到了同样的问题,但原因不同。 但是,在 Windows 8 下会发生这种情况,但在 Windows 7 下则不会。以下行似乎导致了该问题。

pProcess.StartInfo.UseShellExecute = False

解决方案是不禁用 UseShellExecute。 我现在收到一个 Shell 弹出窗口,这是不需要的,但比程序等待没有什么特别的事情发生要好得多。 因此,我为此添加了以下解决方法:

pProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden

现在唯一困扰我的是为什么在 Windows 8 下会发生这种情况。

I was having the same issue, but the reason was different. It would however happen under Windows 8, but not under Windows 7. The following line seems to have caused the problem.

pProcess.StartInfo.UseShellExecute = False

The solution was to NOT disable UseShellExecute. I now received a Shell popup window, which is unwanted, but much better than the program waiting for nothing particular to happen. So I added the following work-around for that:

pProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden

Now the only thing bothering me is to why this is happening under Windows 8 in the first place.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文