当前位置:  开发笔记 > 编程语言 > 正文

ProcessStartInfo挂在"WaitForExit"上?为什么?

如何解决《ProcessStartInfo挂在"WaitForExit"上?为什么?》经验,为你挑选了7个好方法。

我有以下代码:

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?如果是这样,我该怎么做呢?如果没有,我做错了什么?



1> Mark Byers..:

问题是,如果您重定向StandardOutput和/或StandardError内部缓冲区可能已满.无论您使用哪种订单,都可能存在问题:

如果您在读取StandardOutput进程之前等待进程退出,则可以阻止尝试写入进程,因此进程永远不会结束.

如果您从StandardOutput使用ReadToEnd 读取,那么您的进程可以阻止进程永远不会关闭StandardOutput(例如,如果它永远不会终止,或者它被阻止写入StandardError).

解决方案是使用异步读取来确保缓冲区不会满.要避免任何死锁并收集两者的所有输出StandardOutput,StandardError您可以这样做:

编辑:请参阅下面的答案,了解如何在发生超时时避免ObjectDisposedException.

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


不知道重定向输出是导致问题,但果然确实如此.花了4个小时敲打我的头,并在阅读你的帖子后5分钟内修好.干得好!
每次命令提示符关闭时,都会出现:mscorlib.dll中出现未处理的"System.ObjectDisposed"类型的异常附加信息:安全句柄已关闭
我们遇到了类似上面@ user1663380所描述的问题.你是否认为这是可能的,`的事件处理程序using`语句必须_above_的过程本身`using`声明?
我认为不需要等待句柄.根据msdn,只需使用WaitForExit的非超时版本完成:当标准输出被重定向到异步事件处理程序时,当此方法返回时,输出处理可能无法完成.要确保已完成异步事件处理,请在从此重载接收到true后调用WaitForExit()重载,该重载不带参数.

2> Rob..:

在您等待之前说要阅读的文档,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();


我不是百分之百肯定,如果这只是我的环境的结果,但我发现如果你设置`RedirectStandardOutput = true;`并且不使用`p.StandardOutput.ReadToEnd();`你得到一个死锁/挂.
真正.我处于类似的情况.在进程中使用ffmpeg进行转换时,我无缘无故地重定向StandardError,它在StandardError流中写入足够的内容来创建死锁.

3> stevejay..:

Mark Byers的回答非常好,但我只想添加以下内容:在outputWaitHandle和errorWaitHandle被释放之前,需要删除OutputDataReceived和ErrorDataReceived委托.如果进程在超出超时后继续输出数据然后终止,则在处理后将访问outputWaitHandle和errorWaitHandle变量.

(仅供参考我不得不加上这个警告作为答案,因为我无法对他的帖子发表评论.)


@ianbailey最简单的解决方法是将using(Process p ...)放在using(AutoResetEvent errorWaitHandle ...)中
也许最好调用[CancelOutputRead](http://msdn.microsoft.com/zh-cn/library/system.diagnostics.process.canceloutputread.aspx)?

4> Karol Tyl..:

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

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);
        }
    }
}



5> Muhammad Reh..:

这是一个更现代的等待,基于任务并行库(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 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(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;
    }
}

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

    var taskCompletionSource = new TaskCompletionSource();

    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;
}

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

    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;
}


迄今为止最好,最完整的答案

6> Jon..:

罗布回答了这个问题并为我节省了几个小时的试验.在等待之前读取输出/错误缓冲区:

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


@knocte你试图理解微软创建的API?

7> torial..:

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

请尝试以下方法:

1)向p.WaitForExit(nnnn)添加超时; 其中nnnn以毫秒为单位.

2)在WaitForExit调用之前放入ReadToEnd调用.这就是我们所见过的MS推荐.

推荐阅读
帆侮听我悄悄说星星
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有