Asynchronous

  • 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 a Task<Task>, and the code only waits on the outer task. Correct code would be either await Task.Factory.StartNew(ProcessAsync).Unwrap() or await 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 (using TaskCreationOptions.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 or using) :

    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.

Learn More