在异步方法中返回和等待任务之间的区别

Jam*_*mes 5 .net c# task-parallel-library async-await

以下方法之间有什么区别吗?一个比另一个好吗?

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);
}
Run Code Online (Sandbox Code Playgroud)

该方法将从MVC控制器方法调用; 例如:

public async Task<ActionResult> RegisterUser(RegisterViewModel model)
{
  // ...
  await Mailer.SendAsync(user.Email, subject, body);
  return View(model);
}
Run Code Online (Sandbox Code Playgroud)

i3a*_*non 7

有两个实际差异:

  1. 第二个选项不会创建允许async-await使用的状态机机制.这将对绩效产生轻微的积极影响.
  2. 异常处理会有所不同.标记方法时,async任何异常都存储在返回的任务中(来自异步部分和同步部分),并且仅在等待(或等待)任务时抛出.如果不是这样async,同步部分的异常就像任何其他方法一样.

我的建议:使用第二个增加性能提升,但要注意异常和错误.


显示差异的示例:

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);
}
Run Code Online (Sandbox Code Playgroud)

输出:

1-async
2-sync
Run Code Online (Sandbox Code Playgroud)


Yuv*_*kov 5

你的第一个方法await将导致编译器创建一个状态机,因为一旦the await关键字被命中,该方法将返回给调用者,一旦等待的部分完成,它必须从它停止的点恢复.

但是,由于等待后你没有做任何事情(没有延续),所以不需要那个状态机.

在这种情况下,第二种方法是首选,您可以省略该async关键字,因为您没有等待任何事情


Mar*_*son 5

首先,我不认为您的示例中的代码编译.您需要从SendAsync2中删除"async"关键字.

如果你这样做,那么这些方法可以互换使用,所以不,这种情况没有区别.我更喜欢没有异步/等待的那个.

但是,有些情况似乎没有区别,但差异在于细节.考虑例如这段代码:

async Task<X> Get() 
{
     using (something) 
     {
          return await GetX();
     }
}
Run Code Online (Sandbox Code Playgroud)

如果您要将其更改为:

Task<X> Get() 
{
    using (something)
    {
        return GetX(); 
    }
}
Run Code Online (Sandbox Code Playgroud)

然后该using块不再保护封装在x中的执行,并且something将比在第一种情况下更早地处置.例如,什么something是实体框架上下文时很重要.

这同样适用于return await内部try块.