process.WaitForExit()异步

Dus*_*etz 43 .net c# user-interface asynchronous winforms

我想等待一个进程完成,但process.WaitForExit()挂起我的GUI.是否有基于事件的方式,或者我是否需要生成一个线程来阻止直到退出,然后自己委托事件?

MgS*_*Sam 86

从.NET 4.0/C#5开始,使用异步模式表示它更好.

/// <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 canceled.</param>
/// <returns>A Task representing waiting for the process to end.</returns>
public static Task WaitForExitAsync(this Process process, 
    CancellationToken cancellationToken = default(CancellationToken))
{
    var tcs = new TaskCompletionSource<object>();
    process.EnableRaisingEvents = true;
    process.Exited += (sender, args) => tcs.TrySetResult(null);
    if(cancellationToken != default(CancellationToken))
        cancellationToken.Register(tcs.SetCanceled);

    return tcs.Task;
}
Run Code Online (Sandbox Code Playgroud)

用法:

public async void Test() 
{
   var process = new Process("processName");
   process.Start();
   await process.WaitForExitAsync();

   //Do some fun stuff here...
}
Run Code Online (Sandbox Code Playgroud)

  • 如果在我们注册退出处理程序之前进程停止,我们将永远等待.注册必须在Start之前完成,因此编写StartAsync方法要容易得多.大多数相同的代码,但在返回行之前使用process.Start()juste命名为StartAsync. (7认同)
  • 请注意,此处的取消​​代码不会终止进程,因此如果挂起它的进程将保持运行状态.如果要在取消时停止该过程,请更改为以下内容:cancellationToken.Register(()=> {process.Kill(); tcs.SetCanceled();}); (5认同)
  • @LonelyPixel IMO,`WaitForExit()`将在进程运行期间阻塞一个线程.取决于`TaskScheduler`,通常不是调用者,而是来自`ThreadPool`的一个线程.答案的解决方案可能不会阻止ThreadPool线程.(这对你来说可能不是问题.) (3认同)
  • 谢谢,这很有帮助。我遇到的问题是,如果任务已经取消,`tcs.SetResult(null)`会抛出InvalidOperationException`,如果取消任务后进程退出,则可能会发生这种情况。为了解决这个问题,我将tcs.SetResult替换为tcs.TrySetResult。 (2认同)
  • @LonelyPixel 因为它会在进程运行的所有时间阻塞线程? (2认同)
  • 每次有人输入`await Task.Run`,就会有一只小狗死去。 (2认同)

Rya*_*yan 13

这是一个稍微清晰的扩展方法,因为它清除了取消令牌注册和退出事件.它还处理竞争条件边缘情况,其中进程可以在它开始之后但在连接Exited事件之前结束.它使用C#7中的新本地函数语法.

public static class ProcessExtensions
{
    public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken = default)
    {
        var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);

        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.ConfigureAwait(false);
            }
        }
        finally
        {
            process.Exited -= Process_Exited;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Dmi*_*kin 9

如果你选择@MgSam答案,请注意,如果你通过了WaitForExitAsync一些CancellationToken,那将在指定的延迟后自动取消,你可以得到一个InvalidOperationException.要解决这个问题,你需要改变

cancellationToken.Register(tcs.SetCanceled);
Run Code Online (Sandbox Code Playgroud)

cancellationToken.Register( () => { tcs.TrySetCanceled(); } );
Run Code Online (Sandbox Code Playgroud)

PS:别忘了及时处理你CancellationTokenSource.


WBu*_*uck 8

截至目前, Process提供了.NET5一个WaitForExitAsync class

await process.WaitForExitAsync( token );
Run Code Online (Sandbox Code Playgroud)

  • 对于.NET Framework,我推荐[CliWrap](https://github.com/Tyrrrz/CliWrap) (2认同)

BFr*_*ree 5

根据此链接,WaitForExit() 方法用于使当前线程等待相关进程终止。但是,Process 确实有一个可以挂钩的 Exited 事件。

  • 我猜之前的评论者已经在这个答案中想象了“不是”这个词?没有一个。 (2认同)

Dav*_*nar 5

Ryan 解决方案在 Windows 上运行良好。在 OSX 上发生了奇怪的事情,可能是死锁tcs.TrySetResult()!有2种解决方案:

第一:

包装tcs.TrySetResult()到 Task.Run():

    public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken = default)
    {
        var tcs = new TaskCompletionSource<bool>();

        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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

有关此内容和更多详细信息的对话: 以非阻塞方式调用 TaskCompletionSource.SetResult

第二个:

    public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken)
    {
        while (!process.HasExited)
        {
            await Task.Delay(100, cancellationToken);
        }
    }
Run Code Online (Sandbox Code Playgroud)

您可以根据您的应用程序将轮询间隔从 100 毫秒增加到更长。