process.WaitForExit() asynchronously

C#.NetWinformsUser InterfaceAsynchronous

C# Problem Overview


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?

C# Solutions


Solution 1 - C#

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

Solution 2 - C#

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

Solution 3 - C#

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

Solution 4 - C#

If you choose @MgSam answer, be aware, if you pass through WaitForExitAsync some CancellationToken, that will be automatically canceled after the specified delay, you can get an InvalidOperationException. To fix that, you need to change

cancellationToken.Register(tcs.SetCanceled);

to

cancellationToken.Register( () => { tcs.TrySetCanceled(); } );
  

P.S.: don't forget to dispose your CancellationTokenSource in time.

Solution 5 - C#

As of .NET5 there is now a WaitForExitAsync provided by the Process class.

await process.WaitForExitAsync( token );

Solution 6 - C#

According to this link the WaitForExit() method is used to make the current thread wait until the associated process terminates. However, the Process does have an Exited event that you can hook into.

Solution 7 - C#

Ryan solution works good on windows. On OSX strange things happened it could be a Deadlock at tcs.TrySetResult()! There are 2 solutions:

First one:

Wrap tcs.TrySetResult() to a 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;
		}
	}

Conversation about this and more details: https://stackoverflow.com/questions/19481964/calling-taskcompletionsource-setresult-in-a-non-blocking-manner

Second one:

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

You can increase the Polling interval from 100 ms to more depending your application.

Solution 8 - C#

Fast and simple solution:

async Task RunProcessWait(string Path)
{
    Process MyProcess = Process.Start(Path);
    while (!MyProcess.HasExited)
        await Task.Delay(100);
}

await RunProcessWait("C:\...")

Solution 9 - C#

Use System.Diagnostics.Process.Exited

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDustin GetzView Question on Stackoverflow
Solution 1 - C#MgSamView Answer on Stackoverflow
Solution 2 - C#Timothy CarterView Answer on Stackoverflow
Solution 3 - C#RyanView Answer on Stackoverflow
Solution 4 - C#Dmitriy NemykinView Answer on Stackoverflow
Solution 5 - C#WBuckView Answer on Stackoverflow
Solution 6 - C#BFreeView Answer on Stackoverflow
Solution 7 - C#David MolnarView Answer on Stackoverflow
Solution 8 - C#Marco ConcasView Answer on Stackoverflow
Solution 9 - C#Matt BrunellView Answer on Stackoverflow