Await or Task.FromResult

It doesn't really matter. If you're comfortable with always marking Task-returning methods with the async keyword then go ahead and use DoSomething1.

As you said, it's a tradeoff:

  • DoSomething2 doesn't generate the state machine needed for an async method and so it's slightly faster (but the difference is mostly negligible).

  • On the other hand it can have some unforeseen side effects regarding exception handling since in an async method the exception would be stored in the returned Task and in the other it would be thrown regularly.


If you're worried about it, cache the Task:

static readonly Task<bool> falseTask = Task.FromResult(false);

The async keyword also wraps exceptions up in the returned Task, along with a proper stack trace. It's a tradeoff, safety of behavior for perf.

Lets look at the difference scenarios where each would be different:

async Task UseSomething1Async(string someParameter)
{
    // if IsNullOrWhiteSpace throws an exception, it will be wrapped in
    // the task and not thrown here.
    Task t1 = DoSomething1Async(someParameter);

    // rather, it'll get thrown here. this is best practice,
    // it's what users of Task-returning methods expect.
    await t1;

    // if IsNullOrWhiteSpace throws an exception, it will
    // be thrown here. users will not expect this.
    Task t2 = DoSomething2Async(someParameter);

    // this would never have been reached.
    await t2;
}

Just illustrating the point here -- IsNullOrWhiteSpace does not actually throw any exceptions for any reason.

As far as stack traces go, async stack traces are determined by where you await. No await means the method will disappear from the stack trace.

Say DoSomeExpensiveCheckAsync throws an exception. In the case of DoSomething1Async, the stack trace will look like caller -> DoSomething1Async -> DoSomeExpensiveCheckAsync.

In the case of DoSomething2Async, the stack trace would look like caller -> DoSomeExpensiveCheckAsync. Depending on the complexity of your code, this can make things difficult to debug.

In practice, I will generally only directly return a Task if I knew no exceptions would be thrown prior to it, and if the method name was merely an overload forwarding to another overload. There are always exceptions to this rule, there are bound to be places you want to maximize performance. Just pick and choose carefully, realize you might be making the life of you and your user harder.