Perhaps an unpopular opinion, but these days I don't use ConfigureAwait(false)
even in libraries, see:
"Revisiting Task.ConfigureAwait(continueOnCapturedContext: false)
"
IMO, if the code that consumes a Task
-based API is concerned about the current synchronization context and how it might affect that API's behavior (deadlocks, redundant context switches, etc.), it can explicitly wrap the API invocation with Task.Run
or use something like TaskExt.WithNoContext
from the above link:
await Task.Run(() => InitAsync());
// or
await TaskExt.WithNoContext(() => InitAsync());
In most cases though, especially for UI apps (where there's a synchronization context, but threading scalability is not an issue), it's OK to leave it as is, without Task.Run
or ConfigureAwait
:
await InitAsync();
This would give you a chance to discover and investigate potential deadlocks, before trying to mitigate them with ConfigureAwait(false)
or Task.Run
.
So, it is not always a bad idea to continue on the same synchronization context, especially inside async void
methods where unhandled exceptions are posted to the current synchronization context, see TAP global exception handler.
Updated to answer the questions in the comments:
What is the difference between await Task.Run(() => InitAsync());
and
Task.Run(async () => await InitAsync());
and await Task.Run(async () => await InitAsync());
In this case (a simple async
lambda to Task.Run
) the difference would be just an extra overhead of async/await compiler-generated state machine, which you don't need. The task, returned by InitAsync
, will get unwrapped by Task.Run
automatically, either way. For more general cases, see "Any difference between "await Task.Run(); return;" and "return Task.Run()"?".
I'd use an async
lambda here only if I needed to do something else after the completion of InitAsync
, while still not having to worry about synchronization context, e.g.:
await Task.Run(async() => {
await InitAsync();
// we're on a pool thread without SynchronizationContext
log("initialized");
});
Double check: use discard like this _ = WorkAsync();
to suppress
warning, but it doesn't catch exception. To handle exception, I need
to define an extension method like Forget
. on
Fire and Forget approach
Yes, that'd be my choice for fire-and-forget. However, I don't think your InitAsync
is a true fire-and-forget in your case. Perhaps, it'd be better to keep track of it in the class instance: _task = InitAsync()
and observe _task
later.
Or, better yet, you could use an async void
helper method inside OnCreate
to observe the result/exceptions of InvokeAsync
:
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
async void function invokeInitAsync()
{
try
{
await InitAsync();
Finish();
}
catch(Exception e) {
// handle the failure to initialize
await promptAndExitUponErrorAsync(e);
}
}
invokeInitAsync();
}
It might be possible to make OnCreate
itself async void
, but then exceptions (if any) from base.OnCreate()
wouldn't be getting synchronously propagated to the caller of your override, which may have other side effects. Thus, I'd use a helper async void
method, which can be also local as above.
Finally, consider embracing asynchrony in your ViewModel
layer, and then you woudn't have to worry about it in places like OnCreate
. For more details, see: "How to Unit test ViewModel with async initialization in WPF".