process.WaitForExit() asynchronously
C#.NetWinformsUser InterfaceAsynchronousC# 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