Asynchronous
On this page
✅ DO always favor Task.Run to start a background task, unless you have a reason to use
Task.Factory.StartNew
.Task.Run uses safer defaults, and more importantly it automatically unwraps the return task, which can prevent subtle errors with async methods. Consider the following program:
class Program { public static async Task ProcessAsync() { await Task.Delay(2000); Console.WriteLine("Processing done"); } static async Task Main(string[] args) { await Task.Factory.StartNew(ProcessAsync); Console.WriteLine("End of program"); Console.ReadLine(); } }
Despite the appearances, “End of program” will be displayed before “Processing done”. This is because
Task.Factory.StartNew
is going to return aTask<Task>
, and the code only waits on the outer task. Correct code would be eitherawait Task.Factory.StartNew(ProcessAsync).Unwrap()
orawait Task.Run(ProcessAsync)
.There are only three legitimate use-cases for
Task.Factory.StartNew
:Starting a task on a different scheduler Executing the task on a dedicated thread (using
TaskCreationOptions.LongRunning
) Queuing the task on the threadpool global queue (usingTaskCreationOptions.PreferFairness
)✅ DO use
ConfigureAwait(false)
on each of your await calls if your code may be called from a synchronization context.Note however that
ConfigureAwait
only ever has a meaning when using the await keyword.For instance, this code doesn’t make any sense:
// Using ConfigureAwait doesn't magically make this call safe var result = ProcessAsync().ConfigureAwait(false).GetAwaiter().GetResult();
❌ DO NOT wait synchronously on asynchronous code.
Don’t ever wait synchronously for non-completed tasks. Including but not limited to:
Task.Wait
,Task.Result
,Task.GetAwaiter().GetResult()
,Task.WaitAny
,Task.WaitAll
.As a more general advice, any synchronous dependency between two threadpool threads is susceptible to cause threadpool starvation. The causes of the phenomenon are described in this article .
❌ DO NOT use
async void
.An exception thrown in an
async void
method is propagated to the synchronization context and usually ends up crashing the whole application.If you can’t return a task in your method (for instance because you’re implementing an interface), move the
async
code to another method and call it:interface IInterface { void DoSomething(); } class Implementation : IInterface { public void DoSomething() { // This method can't return a Task, // delegate the async code to another method _ = DoSomethingAsync(); } private async Task DoSomethingAsync() { await Task.Delay(100); } }
❌ AVOID
async
when possible.Out of habit/muscle memory, you might write:
public async Task CallAsync() { var client = new Client(); return await client.GetAsync(); }
While the code is semantically correct, using the
async
keyword is not needed here and can have a significant overhead in hot paths. Try removing it whenever possible:public Task CallAsync() { var client = new Client(); return client.GetAsync(); }
However, keep in mind that you can’t use that optimization when your code is wrapped in blocks (like
try
/catch
orusing
) :public async Task Correct() { using (var client = new Client()) { return await client.GetAsync(); } } public Task Incorrect() { using (var client = new Client()) { return client.GetAsync(); } }
In the incorrect version, since the task isn’t awaited inside of the using block, the client might be disposed before the
call completes.