Don’t use async void
An async method may return Task
, Task<T>
and void
. Natural return types are all but void
.
Any sync method returning void
becomes an async method returning Task
.
example:
// synchronous work
void MyMethod()
{
Thread.sleep(1000);
}
// asynchronous work
async Task MyAsyncMethod()
{
await Task.Delay(1000);
}
When an exception is thrown out of an async Task
or async Task<T>
method that exception is captured and placed on the Task
object. For async void
methods, exceptions aren’t caught.
example:
// DON'T DO THIS - exception not thrown
private async void ThrowExceptionAsync()
{
throw new InvalidOperationException();
}
public void AsyncVoidExceptions_CannotBeCaught()
{
try
{
ThrowExceptionAsync();
}
catch(Exception)
{
// The exception is never caught here!
throw;
}
}
The exception to use async void
are asynchronous event handlers.
example:
private async void button1_clicl(object sender, EventArgs e)
{
await Button1ClickAsync(); // my method
}
Prefer Task.FromResult
over Task.Run
for trivially computed data
For pre-computed results, there’s no need to call Task.Run
which will queue a work item to the thread pool that will immediately complete with the pre-computed value. Instead, use Task.FromResult
to create a task which wraps already computed data.
public Task<int> AddSync(int a, int b)
{
return Task.FromResult(a+b);
}
Don’t use Task.Run
for work that blocks the thread for a long time
Task.Run
is not meant for work that blocks the thread for the lifetime of the application doing background work. Task.Run
queues a thread from the pool, with the assumption that this work will finish quickly.
// DON'T DO THIS - blocks a thread for lifetime
public class QueueProcessor
{
//..
public void StartProcessing()
{
Task.Run(ProcessQueueForLifetime);
}
//..
}
Instead, Task.Factory.StartNew
has an option TaskCreationOptions.LongRunning
public class QueueProcessor
{
//..
public Task StartProcessing() => Task.Factory.StartNew(ProcessQueueForLifetime, TaskCreationOptions.LongRunning);
//..
}
this also has the advantage that it can be easily combined with await
and other methods such as Task.WhenAll
Avoid using Task.Result
and Task.Wait
There are few ways to correctly use Task.Result
and Task.Wait
, so the general advice is to completely avoid them.
Using Task.Result
or Task.Wait
to block waiting on an asynchronous call is much worse than calling a truly synchronous API to block.
Reference(s)
https://docs.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming
https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md