对于相互交织的语句,async/await是否比Task更有优势?
本文关键字:Task 是否 await 于相互 语句 async | 更新日期: 2023-09-27 17:54:10
我有一个方法(通过AJAX请求调用),它在序列的末尾运行。在这种方法中,我保存到数据库,发送电子邮件,在其他api/数据库中查找一堆信息,将事物关联在一起,等等。我只是将原始方法重构为第二个版本,并使用Task
s使其异步化,从而减少了最多2秒的运行时间。我使用Task
s主要是因为它看起来更容易(我在异步/等待方面还没有经验),一些任务依赖于其他任务(如任务C、D和E都依赖于B的结果,B本身取决于A)。基本上所有的任务都是同时开始的(处理只是压缩到电子邮件任务上的Wait()
调用,这以某种方式或另一种方式需要所有其他人完成。我一般都是这样做的,除了8个任务:
public thing() {
var FooTask<x> = Task<x>.Factory.StartNew(() => {
// ...
return x;
});
var BarTask<y> = Task<y>.Factory.StartNew(() => {
// ...
var y = FooTask.Result;
// ...
return y + n;
}
var BazTask<z> = Task<z>.Factory.StartNew(() => {
var y = FooTask.Result;
return y - n;
}
var BagTask<z> = Task<z>.Factory.StartNew(() => {
var g = BarTask.Result;
var h = BazTask.Result;
return 1;
}
// Lots of try/catch aggregate shenanigans.
BagTask.Wait();
return "yay";
}
哦,我还需要回滚以前的东西,如果有什么东西坏了,像删除数据库行,如果电子邮件发送失败,所以有几个级别的尝试/捕获在那里。无论如何,所有这些都有效(令人惊讶的是,第一次尝试就成功了)。我的问题是这种方法是否会受益于被重写为使用async/await而不是Task
s。如果是这样,如果不重新运行另一个方法已经运行或等待的异步方法,那么多依赖场景将如何发挥作用?我猜是一些共享变量?
更新:
// ...
行应该表明任务正在做一些事情,比如查找DB记录。如果我说得不清楚,很抱歉。如果上下文没有预热,大约一半的任务(总共8个)可能需要5秒才能运行,而另一半任务只是收集/组装/处理/使用该数据。
我还需要回滚以前的东西,如果有什么东西坏了,像删除数据库行,如果电子邮件发送失败,所以有几个级别的尝试/捕获在那里。
你会发现async
/await
(与Task.Run
配对而不是StartNew
)将使你的代码更清晰:
var x = await Task.Run(() => {
...
return ...;
});
var y = await Task.Run(() => {
...
return x + n;
});
var bazTask = Task.Run(() => {
...
return y - n;
});
var bagTask = Task.Run(async () => {
...
var g = y;
var h = await bazTask;
return 1;
}
await bagTask;
return "yay";
如果您想要await
完成多个任务,您还可以选择使用Task.WhenAll
。await
的错误处理尤其清晰,因为它没有在AggregateException
中包装异常。
通过AJAX请求调用的
这是个小问题。StartNew
和Task.Run
在ASP.NET上都应该避免。
缩短墙体时间2秒
是的,在ASP上并行处理。. NET(这就是代码当前所做的)将使单个请求执行得更快,但以牺牲可伸缩性为代价。如果服务器对每个请求都进行并行处理,那么服务器将无法处理尽可能多的请求。
保存到数据库,发送电子邮件,在其他api/数据库中查找一堆信息
这些都是I/o受限的操作,而不是cpu受限的操作。因此,理想的解决方案是创建真正异步的I/O方法,然后使用await
(必要时使用Task.WhenAll
)调用它们。通过"真正异步",我的意思是调用底层异步api(例如,HttpClient.GetStringAsync
而不是WebClient.DownloadString
;或实体框架的ToListAsync
而不是ToList
等)。使用StartNew
或Task.Run
就是我所说的"伪异步"。
一旦你有了异步api,你的顶层方法真的变得简单了:
X x = await databaseService.GetXFromDatabaseAsync();
Y y = await apiService.LookupValueAsync(x);
Task<Baz> bazTask = databaseSerivce.GetBazFromDatabaseAsync(y);
Task<Bag> bagTask = apiService.SecondaryLookupAsync(y);
await Task.WhenAll(bazTask, bagTask);
Baz baz = await bazTask;
Bag bag = await bagTask;
return baz + bag;