平行的.Invoke不等待异步方法完成
本文关键字:异步方法 等待 Invoke | 更新日期: 2023-09-27 18:09:15
我有一个应用程序,它从不同的来源提取相当数量的数据。本地数据库、网络数据库和web查询。这些都需要几秒钟才能完成。所以,首先我决定并行运行它们:
Parallel.Invoke(
() => dataX = loadX(),
() => dataY = loadY(),
() => dataZ = loadZ()
);
正如预期的那样,所有三个并行执行,但是整个块的执行直到最后一个执行完才会返回。
接下来,我决定添加一个旋转器或"忙碌指示器"。到应用程序。我不想阻塞UI线程,否则旋转器不会旋转。所以这些需要在async
模式下运行。但是如果我在async
模式下运行这三个,那么它们实际上是"同步"发生的,而不是在与UI相同的线程中。我还是想让它们并行运行
spinner.IsBusy = true;
Parallel.Invoke(
async () => dataX = await Task.Run(() => { return loadX(); }),
async () => dataY = await Task.Run(() => { return loadY(); }),
async () => dataZ = await Task.Run(() => { return loadZ(); })
);
spinner.isBusy = false;
现在,Parallel.Invoke
不等待方法完成,旋转器立即关闭。更糟糕的是,dataX/Y/Z为空,稍后会出现异常。
这里正确的方法是什么?我应该用BackgroundWorker
代替吗?我希望能利用。net 4.5的特性
听起来你真的想要这样的东西:
spinner.IsBusy = true;
try
{
Task t1 = Task.Run(() => dataX = loadX());
Task t2 = Task.Run(() => dataY = loadY());
Task t3 = Task.Run(() => dataZ = loadZ());
await Task.WhenAll(t1, t2, t3);
}
finally
{
spinner.IsBusy = false;
}
这样你就可以异步地等待所有任务完成(Task.WhenAll
返回一个任务,当所有其他任务完成时完成),而不会阻塞UI线程…而Parallel.Invoke
(和Parallel.ForEach
等)是阻塞调用,不应该在UI线程中使用。
(Parallel.Invoke
没有阻塞你的异步lambdas的原因是,它只是等待,直到每个Action
返回…也就是当它到达await的start时。通常情况下,您希望将async lambda分配给Func<Task>
或类似的方法,就像您通常不想编写async void
方法一样。
正如您在问题中所述,您的两个方法查询数据库(一个通过sql,另一个通过azure),第三个触发对web服务的POST请求。这三个方法都在执行I/O绑定工作。
当你调用Parallel.Invoke
时,你基本上触发三个ThreadPool线程阻塞并等待基于I/O的操作完成,这几乎是对资源的浪费,如果你需要的话,将会非常糟糕。
相反,您可以使用所有三个都公开的异步api:
- SQL Server通过实体框架6或ADO。净
- Azure有异步api的
- 通过
HttpClient.PostAsync
发送Web请求
让我们假设以下方法:
LoadXAsync();
LoadYAsync();
LoadZAsync();
你可以这样调用它们:
spinner.IsBusy = true;
try
{
Task t1 = LoadXAsync();
Task t2 = LoadYAsync();
Task t3 = LoadZAsync();
await Task.WhenAll(t1, t2, t3);
}
finally
{
spinner.IsBusy = false;
}
这将有相同的预期结果。它不会冻结你的UI,它会让你节省宝贵的资源。