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?

- 34,835
- 7
- 69
- 104

- 21,282
- 15
- 82
- 131
-
Here is a **fully async** implementation of `Process` that lets you also redirect the standard output and standard error streams http://stackoverflow.com/a/39872058/1212017. – Muhammad Rehan Saeed Oct 05 '16 at 11:05
-
While there is no async `process.WaitForExit()`, there is an async `process.StandardOutput.ReadToEnd()`. In case the process you start doesn't close its standard output (long) before it terminates, you may want to consider this as an alternative (although you still have to `process.WaitForExit()` afterward in case that it matters). – Colin Emonds Jul 06 '20 at 16:39
-
The correct answer is currently buried deep down bellow. Search for WBuck's [answer](https://stackoverflow.com/a/67230544/11178549), and the [`WaitForExitAsync`](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.process.waitforexitasync) method. – Theodor Zoulias Apr 15 '22 at 00:59
9 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...
}
-
4Thanks, this was helpful. One thing I had promblems with, though, is that `tcs.SetResult(null)` throws an `InvalidOperationException` if the task was already cancelled, which can happen if the process exits after the task was cancelled. To fix this, I replaced `tcs.SetResult` with `tcs.TrySetResult`. – AJ Richardson Oct 13 '14 at 15:22
-
12If process stop before we register our Exited handler, we are waiting forever. Registration must be done before Start so it's much more easy to write a StartAsync method. Mostly same code but named StartAsync with a process.Start() juste before return line. – MuiBienCarlota Nov 28 '14 at 14:19
-
1This is true- the method could check if the process is exited already and return immediately if so. But this might give a false sense of security as it wouldn't be thread/process safe. If unexpected exits are a possibility, you don't need a separate method, you just need to call this method before you start the process and keep the task around to await on later. – MgSam Dec 02 '14 at 22:53
-
10Note that the cancelling code here is not killing the process, so if the process if hanging it will be left running. If you want to stop the process when cancelling, change to following: cancellationToken.Register( () => { process.Kill(); tcs.SetCanceled(); }); – wangzq Jan 02 '16 at 03:09
-
1Why not just `await Task.Run(() => process.WaitForExit())` instead of all this eventing stuff? – ygoe Mar 01 '16 at 21:20
-
2@LonelyPixel Because it will block the thread during all time the process is running? – TN. Mar 10 '16 at 16:17
-
@TN Well, no, that's what the `await` is for, and it's already in the code in the answer. Mine is just a whole lot shorter, doing the same thing (I believe). – ygoe Mar 11 '16 at 09:32
-
4@LonelyPixel IMO, `WaitForExit()` will block a thread during the time the process is running. Depending on `TaskScheduler`, usually not the calling one, but one thread from `ThreadPool`. The solution from the answer may not block a ThreadPool thread. (It may not be a problem for you.) – TN. Mar 11 '16 at 11:19
-
2
-
To avoid deadlock and avoid writing StartAsync func one can add after "process.Exited +=" a code "if (process.HasExited) tcs.TrySetResult(null);" – norekhov Sep 06 '17 at 07:47
-
Good solution. I needed the exit code from the process, so I changed the return type from `Task` to `Task
` and the `TrySetResult` argument from `null` to `process.ExitCode`. – dlf May 15 '18 at 13:03 -
it's worth to read @Dmitriy Nemykin [answer](https://stackoverflow.com/a/32994778/2377787), helped me a lot – inwenis Mar 07 '19 at 11:29
-
-
1The opposite of the [first comment](https://stackoverflow.com/questions/470256/process-waitforexit-asynchronously/19104345#comment41348599_19104345) is also true: if the task completes and then the cancellation source fires, it [gives `InvalidOperationException` too](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcompletionsource-1.setcanceled?view=netframework-4.8#exceptions). Should be `.Register(() => tcs.TrySetCanceled())`. – GSerg May 29 '19 at 14:14
-
1@MgSam How to add TimeOut using Async implementation? Currently I use Process.WaitForExit(Int32) with TimeOut and would like to turn it into Async but keep timeout. – Tomas Jun 04 '19 at 14:59
-
1Last line of async method should be: return process.HasExited ? Task.CompletedTask : tcs.Task; In case the process has exited before we listen to the event. – J Keegan Jul 04 '19 at 15:36
-
1```process.EnableRaisingEvents = true;``` after the process has already exited causes an ```InvalidOperationException``` . – Niklas Peter Jan 19 '21 at 07:57
-
Concerning my previous comment about the `InvalidOperationException`: https://github.com/dotnet/runtime/issues/1785 – Niklas Peter Apr 27 '21 at 07:43
-
is `CancellationTokenRegistration` from `.Register(...)` auto dispose? – Trương Quốc Khánh Dec 25 '21 at 04:30
process.EnableRaisingEvents = true;
process.Exited += [EventHandler]

- 7,214
- 5
- 36
- 45

- 15,459
- 7
- 44
- 62
-
Be sure to also test for `process.HasExited` after adding the event handler, in case the process exits before that. – ygoe Jul 25 '20 at 06:43
-
@ygoe, It's probably easier to set `Exited` _before_ setting `EnableRasingEvents`. It should also be noted that this will not work for elevated target processes because they throw `Win32Exception` when you try to set `EnableRasingEvents` (or read the `HasExited` or `Handle` properties). – skst Aug 29 '20 at 00:49
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;
}
}
}
}

- 1,670
- 18
- 25
-
-
1@DavidMolnar I think I've tracked down the deadlock, and have added it to the answer. The solution is effectively equivalent to your answer, but uses the `TaskCreationOptions.RunContinuationsAsynchronously` option instead. – Ryan Nov 28 '19 at 07:08
-
It was not working for me. I still got the deadlock in that case. – David Molnar Jan 13 '20 at 13:43
-
See also [Microsoft.VisualStudio.Threading.AwaitExtensions.WaitForExitAsync](https://github.com/microsoft/vs-threading/blob/master/src/Microsoft.VisualStudio.Threading/AwaitExtensions.cs) ([Documentation](https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.threading.awaitextensions.waitforexitasync)). They return the exit code but don't use async continuation. – ygoe Jul 25 '20 at 06:53
-
@DavidMolnar Maybe this helps? https://stackoverflow.com/a/21851153/143684 – ygoe Jul 25 '20 at 07:05
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.

- 240
- 4
- 13
As of .NET5
there is now a WaitForExitAsync provided by the Process class
.
await process.WaitForExitAsync( token );

- 5,162
- 2
- 25
- 36
-
2For .NET Framework, I recommend [CliWrap](https://github.com/Tyrrrz/CliWrap) – Shahin Dohan Jul 01 '21 at 14:47
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.

- 102,548
- 21
- 159
- 201
-
2I’m guessing the prior commenters have imagined the word “not” in this answer? There isn’t one. – bacar Jun 20 '18 at 22:30
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: 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.

- 419
- 1
- 6
- 14
Fast and simple solution:
async Task RunProcessWait(string Path)
{
Process MyProcess = Process.Start(Path);
while (!MyProcess.HasExited)
await Task.Delay(100);
}
await RunProcessWait("C:\...")

- 1,665
- 20
- 25
-
This was all I needed. I also added a check for Abort button in the loop - works great. – sun2sirius Feb 11 '22 at 08:07
-
1