Seems I fell into a massive gotcha and after hours of debugging I saw that process goes out of "using" scope in RunGame1() and the Task never completes when awaited upon.
async void Execute(object o) // ICommand.Execute
{
// fire and forget
try
{
await RunGame2(); // RunGame1() fails to complete
}
finally
{ ... }
}
// This fails due to process going out of "using" scope and never completes
Task RunGame1()
{
var info = new ProcessStartInfo("game.exe") { CreateNoWindow = true };
using var process = new Process() { StartInfo = info };
process.Start();
return process.WaitForExitAsync();
}
// Have to await the Task inside the method
async Task RunGame2()
{
var info = new ProcessStartInfo("game.exe") { CreateNoWindow = true };
using var process = new Process() { StartInfo = info };
process.Start();
await process.WaitForExitAsync();
}
Is there a pattern to get around this so I can return the Task to save the compiler making an extra state machine or is it just something to be aware of?
This is a common gotcha indeed - I've seen several bugs due to similar.
In short:
await a task, and sometimes it can be actively advantageous to avoid the overhead of the async machinery - especially in tight code like file/network IO loop, but (and it is a big but)try/finally region (which also includes using and lock, although there are other bigger problems with lock in async code), then you need to take that into consideration, which usually means "yes, you need to await"It would be nice if there was an analyzer that spotted the return of a Task[<T>]/ValueTask[<T>] in a non-async method, inside such a region, as it is almost always a bug, and for the few times that it isn't (where the finally has nothing to do with the thing being returned) it could be suppressed.