Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

process.WaitForExit() asynchronously

I want to wait for a process to finish, but Process.WaitForExit() hangs my GUI. Is there an event-based way, or do I need to spawn a thread to block until exit, then delegate the event myself?

like image 439
Dustin Getz Avatar asked Jan 22 '09 18:01

Dustin Getz


3 Answers

As of .NET 4.0/C# 5, it's nicer to represent this using the async pattern.

/// <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))
{
    if (process.HasExited) return Task.CompletedTask;

    var tcs = new TaskCompletionSource<object>();
    process.EnableRaisingEvents = true;
    process.Exited += (sender, args) => tcs.TrySetResult(null);
    if(cancellationToken != default(CancellationToken))
        cancellationToken.Register(() => tcs.SetCanceled());

    return process.HasExited ? Task.CompletedTask : tcs.Task;
}

Usage:

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

   //Do some fun stuff here...
}
like image 125
MgSam Avatar answered Oct 13 '22 22:10

MgSam


process.EnableRaisingEvents = true;
process.Exited += [EventHandler]

like image 35
Timothy Carter Avatar answered Oct 13 '22 22:10

Timothy Carter


UPDATE: .NET 5 now includes Process.WaitForExitAsync() natively, you can find the implementation here. It's very similar to the below extension method.

Previous Answer:

Here's an extension method that's slightly cleaner, because it cleans up the cancellation token registration and Exited event. It also handles the race condition edge cases, where the process could end after it started, but before the Exited event was attached. It uses the new local functions syntax in C# 7. The return value is the process return code.

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

        void Process_Exited(object sender, EventArgs e)
        {
             tcs.TrySetResult(process.ExitCode);
        }

        try
        {
            process.EnableRaisingEvents = true;
        }
        catch (InvalidOperationException) when (process.HasExited)
        {
            // This is expected when trying to enable events after the process has already exited.
            // Simply ignore this case.
            // Allow the exception to bubble in all other cases.
        }

        using (cancellationToken.Register(() => tcs.TrySetCanceled()))
        {
            process.Exited += Process_Exited;

            try
            {
            
                if (process.HasExited)
                {
                    tcs.TrySetResult(process.ExitCode);
                }

                return await tcs.Task.ConfigureAwait(false);
            }
            finally
            {
                process.Exited -= Process_Exited;
            }
        }
    }
}
like image 25
Ryan Avatar answered Oct 13 '22 21:10

Ryan