我想等待进程完成,但是process.WaitForExit()挂起了我的GUI。 有没有一种基于事件的方式,还是我需要生成一个线程来阻塞直到退出,然后自己委托该事件?
这是Process的完全异步实现,使您还可以重定向标准输出和标准错误流stackoverflow.com/a/39872058/1212017。
从.NET 4.0 / C#5开始,最好使用异步模式来表示它。
///
/// Waits asynchronously for the process to exit.
///
/// The process to wait for cancellation.
/// A cancellation token. If invoked, the task will return
/// immediately as canceled.
/// A Task representing waiting for the process to end.
public static Task WaitForExitAsync(this Process process,
CancellationToken cancellationToken = default(CancellationToken))
{
var tcs = new TaskCompletionSource();
process.EnableRaisingEvents = true;
process.Exited += (sender, args) => tcs.TrySetResult(null);
if(cancellationToken != default(CancellationToken))
cancellationToken.Register(tcs.SetCanceled);
return tcs.Task;
}
用法:
public async void Test()
{
var process = new Process("processName");
process.Start();
await process.WaitForExitAsync();
//Do some fun stuff here...
}
谢谢,这很有帮助。但是,我遇到的问题是,如果任务已取消,tcs.SetResult(null)会引发InvalidOperationException,如果取消任务后进程退出,则可能会发生这种情况。为了解决这个问题,我用tcs.TrySetResult替换了tcs.SetResult。
@aj_r小提示。谢谢。我做了更新。
如果进程在注册退出的处理程序之前停止,那么我们将永远等待。必须在Start之前完成注册,因此编写StartAsync方法更加容易。大部分是相同的代码,但在返回行之前恰好使用process.Start()命名为StartAsync。
的确如此-该方法可以检查进程是否已经退出,如果已经退出,则立即返回。但这可能会给人一种错误的安全感,因为它不是线程/进程安全的。如果可能发生意外退出,则不需要单独的方法,只需在启动进程之前调用此方法,并保留任务以备稍后使用。
请注意,这里的取消代码不会杀死该进程,因此,如果挂起该进程,它将保持运行状态。如果要在取消时停止该进程,请更改为以下内容:cancelleToken.Register(()=> {process.Kill(); tcs.SetCanceled();});
为什么不只是await Task.Run(() => process.WaitForExit())而不是所有这些事件呢?
@LonelyPixel因为它将在进程运行的所有时间都阻塞线程?
@TN好吧,不,这就是await的作用,它已经在答案的代码中。我的短很多,做同样的事情(我相信)。
@LonelyPixel IMO,WaitForExit()将在进程运行期间阻止线程。取决于TaskScheduler,通常不是调用的那个,而是ThreadPool的一个线程。答案中的解决方案可能不会阻止ThreadPool线程。 (对您来说可能不是问题。)
每当有人键入await Task.Run时,一只小狗就会死亡。
为了避免死锁并避免编写StartAsync函数,可以在" process.Exited + ="之后添加一个代码" if(process.HasExited)tcs.TrySetResult(null);"。
好的解决方案。我需要该过程的退出代码,因此我将返回类型从Task更改为Task,将TrySetResult参数从null更改为process.ExitCode。
值得阅读@Dmitriy Nemykin的答案,对我有很大帮助
死锁警告!非Windows编码器,看看我的答案!
第一条评论的对立面也成立:如果任务完成,然后触发取消源,它也会给出InvalidOperationException。应该是.Register(() => tcs.TrySetCanceled())。
@MgSam如何使用异步实现添加超时?目前,我将Process.WaitForExit(Int32)与TimeOut一起使用,并希望将其转换为Async但保持超时。
异步方法的最后一行应该是:return process.HasExited? Task.CompletedTask:tcs.Task;如果在我们收听事件之前该过程已经退出。
process.EnableRaisingEvents = true;
process.Exited + = [EventHandler]
这是一种更简洁的扩展方法,因为它清除了取消令牌注册和Exited事件。它还处理竞争条件边缘情况,该过程可以在开始后但在附加Exited事件之前结束。它使用C#7中的新本地函数语法。
public static class ProcessExtensions
{
public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken = default)
{
var tcs = new TaskCompletionSource();
void Process_Exited(object sender, EventArgs e)
{
tcs.TrySetResult(true);
}
process.EnableRaisingEvents = true;
process.Exited += Process_Exited;
try
{
if (process.HasExited)
{
return;
}
using (cancellationToken.Register(() => tcs.TrySetCanceled()))
{
await tcs.Task;
}
}
finally
{
process.Exited -= Process_Exited;
}
}
}
死锁警告!非Windows编码器,看看我的答案!
如果选择@MgSam答案,请注意,如果您通过WaitForExitAsync某些CancellationToken,则在指定的延迟后将自动取消,您可以获得InvalidOperationException。要解决此问题,您需要进行更改
cancellationToken.Register(tcs.SetCanceled);
至
cancellationToken.Register( () => { tcs.TrySetCanceled(); } );
附言:别忘了及时处置CancellationTokenSource。
那个PS救了我....
根据此链接,使用WaitForExit()方法使当前线程等待,直到关联的进程终止。但是,该流程确实有一个Exited事件可以挂接到。
那是什么msdn.microsoft.com/zh-CN/library/
不正确,应该删除答案,以免引起我的猜疑。
我猜想以前的评论者曾在此答案中想象过"不"一词吗?没有一个。
Ryan解决方案在Windows上效果很好。在OSX上发生了奇怪的事情,它可能是tcs.TrySetResult()的死锁!有两种解决方案:
第一:
将tcs.TrySetResult()包装到Task.Run():
public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken = default)
{
var tcs = new TaskCompletionSource();
void Process_Exited(object sender, EventArgs e)
{
Task.Run(() => tcs.TrySetResult(true));
}
process.EnableRaisingEvents = true;
process.Exited += Process_Exited;
try
{
if (process.HasExited)
{
return;
}
using (cancellationToken.Register(() => Task.Run(() => tcs.TrySetCanceled())))
{
await tcs.Task;
}
}
finally
{
process.Exited -= Process_Exited;
}
}
有关此问题的对话以及更多详细信息:
以非阻塞方式调用TaskCompletionSource.SetResult
第二个:
public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken)
{
while (!process.HasExited)
{
await Task.Delay(100, cancellationToken);
}
}
您可以将轮询间隔从100毫秒增加到更多,具体取决于您的应用程序。
使用System.Diagnostics.Process.Exited