我们应该如何使用异步等待?

时间:2019-05-14 07:24:48

标签: c# asynchronous async-await

我一直在研究如何使用异步等待,但是当我们有多个互相调用的方法时,我不太明白。我们应该始终使用等待还是仅在实际准备使用结果时才使用等待?

例如,我们应该这样做吗?

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();
}

或者这样:

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

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

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

根据示例1,我们应该在每种方法中始终使用await吗?

根据示例2,当我们开始使用结果时,我们应该仅在最上面的方法上使用await吗?

5 个答案:

答案 0 :(得分:28)

每次调用await时,它都会创建大量代码以捆绑变量,捕获同步上下文(如果适用)并创建延续变成IAsyncStateMachine

本质上,返回没有Task async会给您运行时较低的效率,并为您节省了许多< em> CIL 。请注意, .NET 中的 Async 功能也已经进行了许多优化。还要注意(并且重要的是)在Task语句中返回using可能会引发 Already Disposed Exception

您可以在此处比较 CIL 和管道差异

因此,如果您的方法只是转发Task而又不需要任何东西,则可以轻松地删除async关键字并直接返回Task

更多-因此,有时候我们要做的不只是 forwarding ,而且涉及分支。 Task.FromResultTask.CompletedTask在这里发挥作用,以帮助处理方法中可能出现的逻辑。即,如果您要给出结果(然后返回),或者返回分别返回完成Task

最后,在处理异常时,异步和等待模式有细微的差别。如果返回的是Task,则可以像通常使用Task方法一样,使用Task.FromException<T>在返回的async上弹出任何异常。

无关紧要的例子

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
   }
}

简而言之,如果您不太了解发生了什么,只需await即可;开销将最小。但是,如果您了解如何返回任务结果已完成任务,在任务上放置 exception 或只是< em>转发。您可以保存一些 CIL ,并通过删除async关键字直接返回任务并绕过IAsyncStateMachine来使代码获得少量性能提升。


大约在这个时候,我将查找Stack Overflow用户和作者 Stephen Cleary 和Parallel先生 Stephen Toub 。他们有大量的博客和书籍专门讨论 Async and Await Pattern ,所有陷阱,编码礼节以及许多您肯定会感兴趣的信息。

答案 1 :(得分:12)

两个选项都是合法的,每个选项都有自己的方案,比另一个方案更有效。

当您要处理异步方法的结果或处理当前方法中的可能异常时,当然总是使用await

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

如果在当前方法中不使用异步方法的结果,请返回Task。这种方法将延迟状态机的创建到调用者或将要等待最终任务的地方。正如评论中指出的那样,可以使执行更有效率。

但是在某些情况下,您必须等待任务,即使您对结果什么也不做,也不想处理可能的异常

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

在上述情况下,FindAsync可以返回未完成的任务,并且该任务将立即返回给调用者并处理在context语句中创建的using对象。
稍后当调用者将“等待”任务时,将抛出异常,因为它将尝试使用已处置的对象(context)。

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

传统上有关Async Await的答案必须包括指向Stephen Cleary博客的链接
Eliding Async and Await

答案 2 :(得分:1)

等待是一种排序功能,它使调用者可以接收异步方法的结果并对其执行某些操作。如果不需要处理异步函数的结果,则不必等待它。

在您的示例中,Func1()Func2()不处理被调用的异步函数的返回值,因此最好不要等待它们。

答案 3 :(得分:1)

使用await时,代码将等待异步功能完成。当您需要异步函数中的值时应执行此操作,例如:

int salary = await CalculateSalary();

...

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

如果您没有使用await,则会发生这种情况:

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
}

等待意味着,等待此异步功能完成。

根据需要使用它,情况1和情况2会产生相同的结果,只要您等待分配信息值时代码便是安全的。

来源:https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/index

答案 4 :(得分:0)

我相信第二个会这样做,因为等待正在等待返回值。 由于它正在等待Func1()返回值,因此Func1()已经在执行Func2(),该返回值。