async/await 是否会为交织语句提供优于 Task 的优势?
Would async/await provide benefit over Task for intertwined statements?
我有一个方法(通过 AJAX 请求调用)在序列末尾 运行s。在这种方法中,我保存到数据库、发送电子邮件、在其他 APIs/databases 中查找一堆信息、将事物关联在一起等等。我只是将原始方法重构为第二个修订版并使用 Task
s 使其异步,并在墙上时间缩短了两秒。我使用 Task
s 主要是因为它看起来更容易(我还没有 async/await 的经验)并且一些任务依赖于其他任务(比如任务 C D 和 E 都依赖于 B 的结果,它本身取决于 A)。基本上所有的任务都是同时开始的(处理只是压缩到电子邮件任务的 Wait()
调用,这以一种或另一种方式需要所有其他任务来完成。我通常做这样的事情除了有八个任务:
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";
}
哦,如果出现问题,我还需要回滚以前的东西,比如如果电子邮件发送失败,则删除数据库行,所以那里有几个级别的 try/catches。无论如何,所有这些都有效(令人惊讶的是,第一次尝试就全部成功)。我的问题是这种方法是否会受益于重写为使用 async/await 而不是 Task
s。如果是这样,那么在不重新 运行 已经 运行 或等待另一个方法的异步方法的情况下,多重依赖场景将如何发挥作用?我猜是一些共享变量?
更新:
// ...
行应该表示该任务正在做某事,比如查找数据库记录。抱歉,如果不清楚。如果上下文没有预热,大约一半的任务(总共 8 个)最多可能需要五秒 运行,而另一半的任务只是 collect/assemble/process/use 该数据。
I also need to roll back previous things if something breaks, like remove a database row if the email fails to send, so there are a few levels of try/catches in there.
您会发现 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
.
中包装异常
不过
called via an AJAX request
这有点问题。在 ASP.NET.
上应避免 StartNew
和 Task.Run
shaved off up to two seconds in wall time
是的,ASP.NET 上的并行处理(代码当前正在执行的操作)将使 单个 请求执行得更快,但 在可扩展性的代价。如果对每个请求进行并行处理,服务器将无法处理尽可能多的请求。
save to the database, send emails, look up a bunch of info in other APIs/databases
这些都是 I/O-bound 操作,不受 CPU 限制。因此 理想的 解决方案是创建真正异步的 I/O 方法,然后使用 await
(如果需要,还可以使用 Task.WhenAll
)调用它们。 "truly-async" 是指调用底层异步 API(例如,HttpClient.GetStringAsync
而不是 WebClient.DownloadString
;或 Entity Framework 的 ToListAsync
而不是 ToList
, ETC)。使用 StartNew
或 Task.Run
就是我所说的 "fake asynchrony".
一旦你有了异步 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;
我有一个方法(通过 AJAX 请求调用)在序列末尾 运行s。在这种方法中,我保存到数据库、发送电子邮件、在其他 APIs/databases 中查找一堆信息、将事物关联在一起等等。我只是将原始方法重构为第二个修订版并使用 Task
s 使其异步,并在墙上时间缩短了两秒。我使用 Task
s 主要是因为它看起来更容易(我还没有 async/await 的经验)并且一些任务依赖于其他任务(比如任务 C D 和 E 都依赖于 B 的结果,它本身取决于 A)。基本上所有的任务都是同时开始的(处理只是压缩到电子邮件任务的 Wait()
调用,这以一种或另一种方式需要所有其他任务来完成。我通常做这样的事情除了有八个任务:
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";
}
哦,如果出现问题,我还需要回滚以前的东西,比如如果电子邮件发送失败,则删除数据库行,所以那里有几个级别的 try/catches。无论如何,所有这些都有效(令人惊讶的是,第一次尝试就全部成功)。我的问题是这种方法是否会受益于重写为使用 async/await 而不是 Task
s。如果是这样,那么在不重新 运行 已经 运行 或等待另一个方法的异步方法的情况下,多重依赖场景将如何发挥作用?我猜是一些共享变量?
更新:
// ...
行应该表示该任务正在做某事,比如查找数据库记录。抱歉,如果不清楚。如果上下文没有预热,大约一半的任务(总共 8 个)最多可能需要五秒 运行,而另一半的任务只是 collect/assemble/process/use 该数据。
I also need to roll back previous things if something breaks, like remove a database row if the email fails to send, so there are a few levels of try/catches in there.
您会发现 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
.
不过
called via an AJAX request
这有点问题。在 ASP.NET.
上应避免StartNew
和 Task.Run
shaved off up to two seconds in wall time
是的,ASP.NET 上的并行处理(代码当前正在执行的操作)将使 单个 请求执行得更快,但 在可扩展性的代价。如果对每个请求进行并行处理,服务器将无法处理尽可能多的请求。
save to the database, send emails, look up a bunch of info in other APIs/databases
这些都是 I/O-bound 操作,不受 CPU 限制。因此 理想的 解决方案是创建真正异步的 I/O 方法,然后使用 await
(如果需要,还可以使用 Task.WhenAll
)调用它们。 "truly-async" 是指调用底层异步 API(例如,HttpClient.GetStringAsync
而不是 WebClient.DownloadString
;或 Entity Framework 的 ToListAsync
而不是 ToList
, ETC)。使用 StartNew
或 Task.Run
就是我所说的 "fake asynchrony".
一旦你有了异步 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;