C# 进程在等待退出时有时挂起
等待退出时进程挂起的原因可能是什么 此代码必须启动powershell脚本,该脚本在内部执行许多操作,例如通过MSBuild开始重新编译代码,但问题可能是它生成的输出太多,并且此代码在等待退出时被卡住,即使在power shell脚本正确执行后也是如此 这有点“奇怪”,因为有时这段代码工作得很好,有时只是卡住了 代码挂起于: process.WaitForExit(processTimeoutMilisons) Powershell脚本以1-2秒的速度执行,同时超时时间为19秒C# 进程在等待退出时有时挂起,c#,C#,等待退出时进程挂起的原因可能是什么 此代码必须启动powershell脚本,该脚本在内部执行许多操作,例如通过MSBuild开始重新编译代码,但问题可能是它生成的输出太多,并且此代码在等待退出时被卡住,即使在power shell脚本正确执行后也是如此 这有点“奇怪”,因为有时这段代码工作得很好,有时只是卡住了 代码挂起于: process.WaitForExit(processTimeoutMilisons) Powershell脚本以1-2秒的速度执行,同时超时时间为19秒 public s
public static (bool Success, string Logs) ExecuteScript(string path, int ProcessTimeOutMiliseconds, params string[] args)
{
StringBuilder output = new StringBuilder();
StringBuilder error = new StringBuilder();
using (var outputWaitHandle = new AutoResetEvent(false))
using (var errorWaitHandle = new AutoResetEvent(false))
{
try
{
using (var process = new Process())
{
process.StartInfo = new ProcessStartInfo
{
WindowStyle = ProcessWindowStyle.Hidden,
FileName = "powershell.exe",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
Arguments = $"-ExecutionPolicy Bypass -File \"{path}\"",
WorkingDirectory = Path.GetDirectoryName(path)
};
if (args.Length > 0)
{
var arguments = string.Join(" ", args.Select(x => $"\"{x}\""));
process.StartInfo.Arguments += $" {arguments}";
}
output.AppendLine($"args:'{process.StartInfo.Arguments}'");
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();
process.WaitForExit(ProcessTimeOutMiliseconds);
var logs = output + Environment.NewLine + error;
return process.ExitCode == 0 ? (true, logs) : (false, logs);
}
}
finally
{
outputWaitHandle.WaitOne(ProcessTimeOutMiliseconds);
errorWaitHandle.WaitOne(ProcessTimeOutMiliseconds);
}
}
}
脚本:
start-process $args[0] App.csproj -Wait -NoNewWindow
[string]$sourceDirectory = "\bin\Debug\*"
[int]$count = (dir $sourceDirectory | measure).Count;
If ($count -eq 0)
{
exit 1;
}
Else
{
exit 0;
}
在哪里
$args[0]=“C:\Program Files(x86)\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\MSBuild.exe”
编辑
在@ingen的解决方案中,我添加了一个小包装器,可以重试执行挂起的MS构建
public static void ExecuteScriptRx(string path, int processTimeOutMilliseconds, out string logs, out bool success, params string[] args)
{
var current = 0;
int attempts_count = 5;
bool _local_success = false;
string _local_logs = "";
while (attempts_count > 0 && _local_success == false)
{
Console.WriteLine($"Attempt: {++current}");
InternalExecuteScript(path, processTimeOutMilliseconds, out _local_logs, out _local_success, args);
attempts_count--;
}
success = _local_success;
logs = _local_logs;
}
其中,
InternalExecuteScript
是ingen的代码不确定这是否是您的问题,但看看MSDN,当您异步重定向输出时,重载的WaitForExit似乎有些奇怪。MSDN文章建议在调用重载方法后调用不带参数的WaitForExit
文档页面位于相关文本中:
当标准输出被重定向到异步事件处理程序时,当此方法返回时,输出处理可能尚未完成。要确保异步事件处理已完成,请调用WaitForExit()重载,该重载在接收到此重载的true后不接受任何参数。要帮助确保在Windows窗体应用程序中正确处理退出的事件,请设置SynchronizingObject属性
代码修改可能如下所示:
if (process.WaitForExit(ProcessTimeOutMiliseconds))
{
process.WaitForExit();
}
问题在于,如果重定向StandardOutput和/或StandardError,内部缓冲区可能会变满 要解决上述问题,您可以在单独的线程中运行该进程。我不使用WaitForExit,而是使用process exited事件,该事件将异步返回流程的ExitCode,以确保其已完成
public async Task<int> RunProcessAsync(params string[] args)
{
try
{
var tcs = new TaskCompletionSource<int>();
var process = new Process
{
StartInfo = {
FileName = 'file path',
RedirectStandardOutput = true,
RedirectStandardError = true,
Arguments = "shell command",
UseShellExecute = false,
CreateNoWindow = true
},
EnableRaisingEvents = true
};
process.Exited += (sender, args) =>
{
tcs.SetResult(process.ExitCode);
process.Dispose();
};
process.Start();
// Use asynchronous read operations on at least one of the streams.
// Reading both streams synchronously would generate another deadlock.
process.BeginOutputReadLine();
string tmpErrorOut = await process.StandardError.ReadToEndAsync();
//process.WaitForExit();
return await tcs.Task;
}
catch (Exception ee) {
Console.WriteLine(ee.Message);
}
return -1;
}
公共异步任务RunProcessAsync(参数字符串[]args)
{
尝试
{
var tcs=new TaskCompletionSource();
var流程=新流程
{
StartInfo={
文件名='文件路径',
重定向标准输出=真,
RedirectStandardError=true,
Arguments=“shell命令”,
UseShellExecute=false,
CreateNoWindow=true
},
EnableRaisingEvents=true
};
process.exit+=(发送方,参数)=>
{
tcs.SetResult(process.ExitCode);
process.Dispose();
};
process.Start();
//在至少一个流上使用异步读取操作。
//同步读取两个流将产生另一个死锁。
process.BeginOutputReadLine();
字符串tmperroout=wait process.StandardError.ReadToEndAsync();
//process.WaitForExit();
返回等待任务;
}
捕获(异常ee){
控制台写入线(ee.Message);
}
返回-1;
}
上面的代码经过战斗测试,使用命令行参数调用FFMPEG.exe。我把mp4文件转换成mp3文件,一次完成1000多个视频,没有失败。不幸的是,我没有直接的power shell经验,但希望这能有所帮助。让我们从相关文章中的概述开始
问题在于,如果重定向StandardOutput和/或StandardError,内部缓冲区可能会变满。无论您使用何种顺序,都可能存在问题:
- 如果在读取StandardOutput之前等待进程退出,进程可能会阻止尝试写入,因此进程永远不会结束
- 如果使用ReadToEnd读取StandardOutput,则如果进程从未关闭StandardOutput(例如,如果它从未终止,或者如果它被阻止写入StandardError),则进程可能会被阻止
//订阅OutputData
Observable.FromEventPattern(进程,名称(进程.OutputDataReceived))
.订阅(
eventPattern=>output.AppendLine(eventPattern.EventArgs.Data),
exception=>error.AppendLine(exception.Message)
).一次性使用(一次性使用);
FromEventPattern
允许我们将事件的不同事件映射到统一的流(也称为可观察)。这允许我们在管道中处理事件(使用类似LINQ的语义)。这里使用的Subscribe
重载提供了Action
和Action
。无论何时引发观察到的事件,其发送方
和参数
都将被事件模式
包装并通过操作
推送。在管道中引发异常时,将使用操作
事件
模式的缺点之一,在本用例中(以及参考文章中的所有解决方法)得到了明确说明,就是不清楚何时/何地取消订阅事件处理程序
使用Rx,我们在订阅时会返回一个IDisposable
。当我们处理它时,我们实际上结束了订阅。Wi
.
.
.
process.BeginOutputReadLine();
process.BeginErrorReadLine();
//First waiting for ReadOperations to Timeout and then check Process to Timeout
if (!outputWaitHandle.WaitOne(ProcessTimeOutMiliseconds) && !errorWaitHandle.WaitOne(ProcessTimeOutMiliseconds)
&& !process.WaitForExit(ProcessTimeOutMiliseconds) )
{
//To cancel the Read operation if the process is stil reading after the timeout this will prevent ObjectDisposeException
process.CancelOutputRead();
process.CancelErrorRead();
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Timed Out");
Logs = output + Environment.NewLine + error;
//To release allocated resource for the Process
process.Close();
return (false, logs);
}
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Completed On Time");
Logs = output + Environment.NewLine + error;
ExitCode = process.ExitCode.ToString();
// Close frees the memory allocated to the exited process
process.Close();
//ExitCode now accessible
return process.ExitCode == 0 ? (true, logs) : (false, logs);
}
}
finally{}
$path1 = """C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\MSBuild.exe"" ""C:\Users\John\source\repos\Test\Test.sln"" -maxcpucount:3"
$cmdOutput = cmd.exe /c $path1 '2>&1'
$cmdOutput
$filepath = "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\MSBuild.exe"
$arg1 = "C:\Users\John\source\repos\Test\Test.sln"
$arg2 = "-m:3"
$arg3 = "-nr:False"
Start-Process -FilePath $filepath -ArgumentList $arg1,$arg2,$arg3 -Wait -NoNewWindow
$path1 = """C:\....\15.0\Bin\MSBuild.exe"" ""C:\Users\John\source\Test.sln"""
$cmdOutput = cmd.exe /c $path1 '2>&1'
$cmdOutput