Should I worry about “This async method lacks ‘await’ operators and will run synchronously” warning

The async keyword is merely an implementation detail of a method; it isn’t part of the method signature. If a particular method implementation or override has nothing to await, then just omit the async keyword and return a completed task using Task.FromResult<TResult>:

public Task<string> Foo()               //    public async Task<string> Foo()
{                                       //    {
    Baz();                              //        Baz();
    return Task.FromResult("Hello");    //        return "Hello";
}                                       //    }

If your method return type is Task instead of Task<TResult>, then return Task.CompletedTask:

public Task Bar()                       //    public async Task Bar()
{                                       //    {
    Baz();                              //        Baz();
    return Task.CompletedTask;          //
}                                       //    }

Note: Task.CompletedTask was added in .NET Framework 4.6. If you’re targeting .NET Framework 4.5.2 or earlier, then you can instead return a completed task of any type and value. Task.FromResult(0) seems to be a popular choice:

public Task Bar()                       //    public async Task Bar()
{                                       //    {
    Baz();                              //        Baz();
    return Task.FromResult(0);          //
}                                       //    }

Dealing with Exceptions

An exception thrown by a non-async method propagates immediately up the call stack, but an exception thrown by an async method is stored in the returned Task object and propagates only when the Task is awaited. This makes a big difference if someone calls your method and then does something else before awaiting the Task:

Task<string> task = Foo();   // If Foo is async and throws an exception,
DoSomethingElse();           // then this line will be executed,
string result = await task;  // and the exception will be rethrown here.

If you need to preserve this behavior for a non-async method, then wrap the entire method within a try…catch statement. Pass any unhandled exception to Task.FromException, and return the result:

public Task<string> Foo()                       //  public async Task<string> Foo()
{                                               //  {
    try                                         //
    {                                           //
        Baz(); // might throw                   //      Baz();
        return Task.FromResult("Hello");        //      return "Hello";
    }                                           //
    catch (Exception ex)                        //
    {                                           //
        return Task.FromException<string>(ex);  //
    }                                           //
}                                               //  }

public Task Bar()                               //  public async Task Bar()
{                                               //  {
    try                                         //
    {                                           //
        Baz(); // might throw                   //      Baz();
        return Task.CompletedTask;              //
    }                                           //
    catch (Exception ex)                        //
    {                                           //
        return Task.FromException(ex);          //
    }                                           //
}                                               //  }

The generic argument to Task.FromException must match the return type of the method.

Reducing Boilerplate Code

You can use the following helper class to automatically call Task.FromResult and Task.FromException for you:

public static class TaskHelper
{
    public static Task FromResultOf(Action action)
    {
        try
        {
            action();
            return Task.CompletedTask;
        }
        catch (Exception ex)
        {
            return Task.FromException(ex);
        }
    }

    public static Task<T> FromResultOf<T>(Func<T> func)
    {
        try
        {
            return Task.FromResult(func());
        }
        catch (Exception ex)
        {
            return Task.FromException<T>(ex);
        }
    }
}

Sample usage:

public Task<string> Foo()               //    public async Task<string> Foo()
{                                       //    {
    return TaskHelper.FromResultOf(     //
        () =>                           //
        {                               //
            Baz();                      //        Baz();
            return "Hello";             //        return "Hello";
        });                             //
}                                       //    }

public Task Bar()                       //    public async Task Bar()
{                                       //    {
    return TaskHelper.FromResultOf(     //
        () =>                           //
        {                               //
            Baz();                      //        Baz();
        });                             //
}                                       //    }

Leave a Comment