process.WaitForExit() asynchronously

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


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...
}

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;
            }
        }
    }
}