我一直在研究如何使用异步等待,但是当我们有多个互相调用的方法时,我不太明白。我们应该始终使用等待还是仅在实际准备使用结果时才使用等待?
例如,我们应该这样做吗?
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吗?
答案 0 :(得分:28)
每次调用await
时,它都会创建大量代码以捆绑变量,捕获同步上下文(如果适用)并创建延续变成IAsyncStateMachine
。
本质上,返回没有Task
的async
会给您运行时较低的效率,并为您节省了许多< em> CIL 。请注意, .NET 中的 Async 功能也已经进行了许多优化。还要注意(并且重要的是)在Task
语句中返回using
可能会引发 Already Disposed Exception 。
您可以在此处比较 CIL 和管道差异
因此,如果您的方法只是转发Task
而又不需要任何东西,则可以轻松地删除async
关键字并直接返回Task
。
更多-因此,有时候我们要做的不只是 forwarding ,而且涉及分支。 Task.FromResult
和Task.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()
,该返回值。