ProcessStartInfo挂上"WaitForExit"?为什么?
-
02-07-2019 - |
题
我有下列代码:
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长。运行它在窗台。不幸的是,编程方式这个挂着无限期地在WaitForExit.还注意到,这并代码没挂对于较小的产出(如3KB).
它是可能的,内部StandardOutput在ProcessStartInfo不能缓冲7MB?如果是这样,我应该怎么做呢?如果不是,我做错了什么?
解决方案
问题是,如果你重定向 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.
}
}
}
其他提示
的文档 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();
Mark Byers的回答非常好,但我只想添加以下内容:在outputWaitHandle和errorWaitHandle被释放之前,需要删除OutputDataReceived和ErrorDataReceived委托。如果进程在超出超时后继续输出数据然后终止,则在处理后将访问outputWaitHandle和errorWaitHandle变量。
(仅供参考我不得不加上这个警告作为答案,因为我无法评论他的帖子。)
当进程超时时,会发生未处理的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);
}
}
}
这是一个更现代的,基于任务并行库(TPL)的.NET 4.5及更高版本的解决方案。
用法示例
try
{
var exitCode = await StartProcess(
"dotnet",
"--version",
@"C:\",
10000,
Console.Out,
Console.Out);
Console.WriteLine(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
}
})
{
process.Start();
var cancellationTokenSource = timeout.HasValue ?
new CancellationTokenSource(timeout.Value) :
new CancellationTokenSource();
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;
}
quot;Process Exited with Exit Code {exitCode}!");
}
catch (TaskCanceledException)
{
Console.WriteLine("Process Timed Out!");
}
实施
<*>// Read the output stream first and then wait.
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
我们也有这个问题(或变体)。
尝试以下方法:
1)向p.WaitForExit(nnnn)添加超时;其中nnnn以毫秒为单位。
2)在WaitForExit调用之前放入ReadToEnd调用。这个 我们看到MS推荐的内容。
感谢 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初始化添加到代码示例
我这样解决了:
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();
我重定向了输入,输出和错误,并处理了输出和错误流的读取。 此解决方案适用于适用于Windows 7和Windows 8的SDK 7-8 8.1
这篇文章可能已经过时,但我发现它通常挂起的主要原因是由于redirectStandardoutput的堆栈溢出或者你有redirectStandarderror。
由于输出数据或错误数据很大,它将导致挂起时间,因为它仍在无限期地处理。
所以要解决这个问题:
p.StartInfo.RedirectStandardoutput = False
p.StartInfo.RedirectStandarderror = False
我尝试通过考虑Mark Byers,Rob,stevejay的答案来创建一个使用异步流读取来解决问题的类。这样做我意识到存在与异步进程输出流读取相关的错误。
我在微软报告了这个错误: https://connect.microsoft.com/的VisualStudio /反馈/信息/ 3119134
要点:
你做不到:
process.BeginOutputReadLine();的Process.Start();
您将收到System.InvalidOperationException:StandardOut具有 未被重定向或过程尚未开始。
=============================================== ================================================== ===========================
然后你必须在进程之后启动异步输出读取 开始:
的Process.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();
=============================================== ================================================== ===========================
然后有些人可能会说你只需阅读流 在将其设置为异步之前。但同样的问题发生了。那里 将是同步读取和设置之间的竞争条件 流进入异步模式。
=============================================== ================================================== ===========================
无法实现输出流的安全异步读取 以实际方式处理过程“过程”和“ProcessStartInfo”。具有 已经设计好了。
您可能更喜欢使用其他用户建议的异步读取。但是你应该知道,由于竞争条件你可能会错过一些信息。
上述答案都没有完成。
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);
}
此代码经过调试并完美运行。
简介
当前接受的答案不起作用(抛出异常)并且有太多的变通方法但没有完整的代码。这显然是在浪费很多人的时间,因为这是一个很受欢迎的问题。
结合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);
}
}
}
}
}
我知道这已经过时了,但在阅读完这一页之后,没有一个解决方案对我有用,虽然我没有尝试过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 = <*>quot;...";
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(<*>quot;Failed run... blah blah");
}
}
else
{
throw new Exception(<*>quot;process timed out after waiting {timeout}");
}
}
catch (Exception e)
{
throw new Exception(<*>quot;Failed to succesfully run the process.....", e);
}
}
}
希望这有助于某些人认为这可能会如此艰难!
在阅读完所有帖子后,我决定采用Marko Avlija&#353;的综合解决方案。 然而,它并没有解决我的所有问题。
在我们的环境中,我们有一个Windows服务,计划运行数百个不同的.bat .cmd .exe,...等文件,这些文件多年来积累并由不同的人和不同风格编写。我们无法控制程序的编写和脚本,我们负责安排,运行和报告成功/失败。
所以我在这里尝试了几乎所有的建议,并取得了不同程度的成功。 Marko的答案几乎是完美的,但是当作为服务运行时,它并不总是捕获标准输出。我从来没有深究其中的原因。
我们发现在我们所有情况下都能解决的唯一解决方案是: http://csharptest.net/319/using-the-processrunner-class/index.html
解决方法我最终使用以避免所有复杂性:
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
所以我创建了一个临时文件,使用&gt;将输出和错误重定向到它outputfile&gt; 2&gt;&amp; 1
然后在过程完成后只读取文件。
其他解决方案适用于您希望使用输出执行其他操作的场景,但对于简单的操作,这可以避免很多复杂性。
我认为这是简单而且更好的方法(我们不需要 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;
}
}
我认为使用async,即使同时使用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
让我们调用此处发布的示例代码重定向器和重定向的其他程序。如果是我,那么我可能会编写一个测试重定向程序,可用于复制问题。
所以我做到了。对于测试数据,我使用了ECMA-334 C#语言规范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(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();
quot;Error: {ex.Message}");
return;
}
datasize值与实际文件大小不匹配,但无关紧要。目前尚不清楚PDF文件是否总是在行尾使用CR和LF,但这对此无关紧要。您可以使用任何其他大型文本文件进行测试。
使用该示例重定向器代码在写入大量数据时挂起,但在写入少量数据时则不挂起。
我非常努力地以某种方式跟踪代码的执行,但我无法。我注释掉了重定向程序的行,这些程序禁止为重定向程序创建控制台以尝试获取单独的控制台窗口,但我不能。
然后我找到如何在新窗口,父窗口或无窗口启动控制台应用程序。显然,当一个控制台程序启动另一个没有ShellExecute的控制台程序时,我们不能(轻松地)拥有一个单独的控制台,并且由于ShellExecute不支持重定向,我们必须共享控制台,即使我们没有为其他进程指定窗口。
我假设如果重定向程序在某处填充缓冲区,那么它必须等待读取数据,如果此时重定向器没有读取任何数据,那么它就是死锁。
解决方案是不使用ReadToEnd并在写入数据时读取数据,但不必使用异步读取。解决方案可以非常简单。以下适用于5 MB PDF文件。
<*>另一种可能性是使用GUI程序进行重定向。上述代码在WPF应用程序中有效,除非有明显的修改。
我遇到了同样的问题,但原因不同了。但它会在Windows 8下发生,但不会在Windows 7下发生。以下行似乎导致了问题。
pProcess.StartInfo.UseShellExecute = False
解决方案是不禁用UseShellExecute。我现在收到一个Shell弹出窗口,这是不需要的,但比等待没有特别发生的程序要好得多。所以我为此添加了以下解决方法:
pProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden
现在唯一困扰我的是为什么在Windows 8下首先发生这种情况。