42

I was looking at how to use async await, but I do not quite get it when we have multiple methods invoking each other. Should we always use await or should we only use await when we are actually ready to use the result?

So for example should we do it like this:

async Task<string[]> FooAsync()
{
    var info = await Func1();
    return info.split('.');
}

async Task<string> Func1()
{
    return await Func2();
}

async Task<string> Func2()
{
    return await tcpClient.ReadStringAsync();
}

Or like this:

async Task<string[]> FooAsync()
{
    var info = await Func1();
    return info.split('.');
}

Task<string> Func1()
{
    return Func2();
}

Task<string> Func2()
{
    return tcpClient.ReadStringAsync();
}

Per example 1, should we always use await in every method?
Or
Per example 2 should we only use await on the top-most method when we start using the result?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Vincent
  • 1,497
  • 1
  • 21
  • 44
  • 7
    Every-time you call `await` it creates a lump of code and goes through a statemachine, by *returning* a *task* you are sometimes creating a small efficiency. However, there are situations that you might want to poke any exceptions on the returned task (that will be *awaited*). i think from here you want to start looking at Stephen Cleary and Stephen Toubs awesome blogs on the Async and Await Pattern – TheGeneral May 14 '19 at 07:29
  • 2
    `It Depends`. If you *don't* want to do anything with the result, you don't need to use `await` and can just return the value. Debugging becomes a bit harder though, as you can't check the return value in `Func2` itself during debugging, nor can you handle exceptions locally. – Panagiotis Kanavos May 14 '19 at 07:31
  • 2
    I can recommend the [AsyncGuidance](https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md#prefer-asyncawait-over-directly-returning-task) from David Fowler. In this case: `Prefer async/await over directly returning Task` – kapsiR May 14 '19 at 07:48
  • Additionaly when you await a task you are not guaranteed to be on the same thread anymore but one could say that is of no interesst. Furthermore awaiting a task in the wrong place could led to deadlocks. – NtFreX May 14 '19 at 08:20

5 Answers5

31

Every-time you call await it creates a lump of code to bundle up variables, captures the synchronization context (if applicable) and create a continuation into an IAsyncStateMachine.

Essentially, returning a Task without the async keyword will give you a small run-time efficiency and save you a bunch of CIL. Do note that the Async feature in .NET also has many optimizations already. Also note (and importantly) that returning a Task in a using statement will likely throw an Already Disposed Exception.

You can compare the CIL and plumbing differences here

So if your method is just forwarding a Task and not wanting anything from it, you could easily just drop the async keyword and return the Task directly.

More-so, there are times when we do more than just forwarding and there is branching involved. This is where, Task.FromResult and Task.CompletedTask come into play to help deal with the logic of what may arise in a method. I.e If you want to give a result (there and then), or return a Task that is completed (respectively).

Lastly, the Async and Await Pattern has subtle differences when dealing with Exceptions. If you are returning a Task, you can use Task.FromException<T> to pop any exception on the the returned Task like an async method would normally do.

Nonsensical example

public Task<int> DoSomethingAsync(int someValue)
{
   try
   {
      if (someValue == 1)
         return Task.FromResult(3); // Return a completed task

      return MyAsyncMethod(); // Return a task
   }
   catch (Exception e)
   {
      return Task.FromException<int>(e); // Place exception on the task
   }
}

In short, if you don't quite understand what is going on, just await it; the overhead will be minimal. However, if you understand the subtitles of how to return a task result, a completed task, placing an exception on a task, or just forwarding. You can save your self some CIL and give your code a small performance gain by dropping the async keyword returning a task directly and bypassing the IAsyncStateMachine.


At about this time, I would look up the Stack Overflow user and author Stephen Cleary, and Mr. Parallel Stephen Toub. They have a plethora of blogs and books dedicated solely to the Async and Await Pattern, all the pitfalls, coding etiquette and lots more information you will surely find interesting.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
TheGeneral
  • 79,002
  • 9
  • 103
  • 141
14

Both options are legit and each option has own scenarios where it is more effective then another.

Of course always use await when you want to handle result of the asynchronous method or handle possible exception in current method

public async Task Execute()
{
    try
    {
        await RunAsync();
    }
    catch (Exception ex)
    {
        // Handle thrown exception
    }
}

If you don't use result of asynchronous method in current method - return the Task. This approach will delay state machine creation to the caller or where ever final task will be awaited. As pointed in the comments can make execution little bit more effective.

But there are scenarios where you must await for the task, even you do nothing with result and don't want handle possible exceptions

public Task<Entity> GetEntity(int id)
{
    using (var context = _contextFactory.Create())
    {
        return context.Entities.FindAsync(id);
    }
}

In the scenario above, FindAsync can return not completed task and this task will be returned straight away to the caller and dispose context object created within using statement.
Later when caller will "await" for the task exception will be thrown because it will try to use already disposed object(context).

public async Task<Entity> GetEntity(int id)
{
    using (var context = _contextFactory.Create())
    {
        return await context.Entities.FindAsync(id);
    }
}

And traditionally answers about Async Await must include link to Stephen Cleary's blog
Eliding Async and Await

Fabio
  • 31,528
  • 4
  • 33
  • 72
1

Await is a sequencing feature which allows the caller to receive the result of an async method and do something with it. If you do not need to process the result of an async function, you do not have to await it.

In your example Func1() and Func2() do no process the return values of the called async functions, so it is fine not to await them.

Phillip Ngan
  • 15,482
  • 8
  • 63
  • 79
1

When you use await the code will wait for the async function to finish. This should be done when you need a value from an async function, like this case:

int salary = await CalculateSalary();

...

async Task<int> CalculateSalary()
{
    //Start high cpu usage task
    ...
    //End high cpu usage task
    return salary;
}

If you hadn't use the the await this would happen:

int salary = CalculateSalary().Result;

...

async Task<int> CalculateSalary()
{
    //Start high cpu usage task
    ... //In some line of code the function finishes returning null because we didn't wait the function to finish
    return salary; //This never runs
}

Await means, wait this async function to finish.

Use it to your needs, your case 1 and 2 would produce the same result, as long as you await when you assign the info value the code will be safe.

Source: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/index

janavarro
  • 869
  • 5
  • 24
0

I believe the 2nd one will do because await is expecting a return value. Since it is waiting for the Func1() to return a value, Func1() is already executing Func2() which is returning a value.