9

Is there any difference between the methods below? Is one preferable over the other?

public static async Task SendAsync1(string to, string subject, string htmlBody) {
  // ...
  await smtp.SendMailAsync(message);
  // No return statement
}

public static Task SendAsync2(string to, string subject, string htmlBody) {
  // ...
  return smtp.SendMailAsync(message);
}

This method will be called from MVC controller methods; for example:

public async Task<ActionResult> RegisterUser(RegisterViewModel model)
{
  // ...
  await Mailer.SendAsync(user.Email, subject, body);
  return View(model);
}
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
James
  • 7,343
  • 9
  • 46
  • 82
  • Maybe the cost of maintaining the state (via the async/await state mechanism) in 2 places if SendAsync1 is used, but maybe the async/await state machine is clever enough to optimise this away...Just a thought, its interesting though. – brumScouse Jun 08 '14 at 20:01

3 Answers3

11

There are 2 practical differences:

  1. The second option will not create the state machine mecanism that allows for async-await usage. That will have a minor positive effect on performance.
  2. The exception handling would be a little different. When you mark a method as async any exceptions are stored in the returned task (both from the asynchronous part and the synchronous one) and thrown only when the task is awaited (or waited). When it's not async, the exceptions from the synchronous parts act just like in any other method.

My suggestion: Use the second one for the added performance boost but keep an eye out for exceptions and bugs.


An example that shows the difference:

public static async Task Test()
{
    Task pending = Task.FromResult(true);
    try
    {
        pending = SendAsync1();
    }
    catch (Exception)
    {
        Console.WriteLine("1-sync");
    }

    try
    {
        await pending;
    }
    catch (Exception)
    {
        Console.WriteLine("1-async");
    }

    pending = Task.FromResult(true);
    try
    {
        pending = SendAsync2();
    }
    catch (Exception)
    {
        Console.WriteLine("2-sync");
    }

    try
    {
        await pending;
    }
    catch (Exception)
    {
        Console.WriteLine("2-async");
    }
}

public static async Task SendAsync1()
{
    throw new Exception("Sync Exception");
    await Task.Delay(10);
}

public static Task SendAsync2()
{
    throw new Exception("Sync Exception");
    return Task.Delay(10);
}

Output:

1-async
2-sync
i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • "will not create the state machine mecanism" - what does it mean?? How do you know and where can you see that? Its like invisible man in invisible lake, I cant understand it at all :( I mean PHYSICALLY - what is going on? – monstro Mar 29 '18 at 16:10
  • @monstro http://sharplab.io/#v2:CYLg1APgAgDABFAjAbgLAChYMQVjejKAZgQCY4BhDAbwznoRKgA4EA2OAZQFMA7YAIIBnAJ68AxogAUASjoNa6BsoQBOLn2ABZAIYBLADbCx42fmUBfefWuN2G/sYmlZtxSoZQA7A+36johJmtlZKDLbE9jz8uoZOpnJh9O4eCD5QbAB0FAD2ALYADgbcAC7cwBnmDKEWQA= – i3arnon Mar 29 '18 at 16:31
5

Your first method which awaits will cause the compiler to create a state machine, since once the await keyword is hit the method will return to the caller, and once the awaited part is completed it has to resume from the point where it left off.

But, since you aren't doing anything after awaiting (there is no continuation), there is no need for that state machine.

The second method is preferred in this case, and you can omit the async keyword from it since you are not awaiting anything

quentin-starin
  • 26,121
  • 7
  • 68
  • 86
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
5

First of all, I don't think that the code in your example compiles. You need to remove the 'async' keyword from SendAsync2.

If you do that, then these methods can be used interchangeably, so no, there is no difference in this case. I would prefer the one without async/await.

However, there are cases where it would seem that there is no difference, but the difference lies in the details. Consider for example this code:

async Task<X> Get() 
{
     using (something) 
     {
          return await GetX();
     }
}

If you were to change this to:

Task<X> Get() 
{
    using (something)
    {
        return GetX(); 
    }
}

then the using block no longer protects the execution encapsulated in x, and something will be disposed earlier than it would in the first case. Important for example when something is a Entity Framework context.

The same goes for return await inside try blocks.

Markus Johnsson
  • 3,949
  • 23
  • 30