【发布时间】:2023-03-27 18:00:01
【问题描述】:
我有大量数据要处理。目前的代码可以简化如下:
public void ProcessData(string data)
{
string resultOfA = doCpuBoundWorkA(data);
string resultOfS1 = sendToServiceS1(resultOfA);
string resultOfB = doCpuBoundWorkB(resultOfS1);
string resultOfS2 = sendToServiceS2(resultOfB);
string resultOfC = doCpuBoundWorkC(resultOfS2);
}
使用 Parallel.ForEach 调用 ProcessData。这种实现方式至少从两个角度来看都不是最优的。首先,对服务的调用是阻塞的,所以我们在等待调用返回时阻塞线程。其次,Parallel.ForEach 创建计划在线程池上执行的任务。线程池每 500 毫秒创建额外的线程(如果我没记错的话),并且因为“ProcessData”需要超过 500 毫秒才能完成,随着时间的推移,我们最终会得到数百个线程,这些线程大部分时间都在等待服务卷土重来。
我对“改进”的幼稚想法是这样的:
public async Task ProcessData(string data)
{
string resultOfA = doCpuBoundWorkA(data);
string resultOfS1 = await sendToServiceS1Async(resultOfA);
string resultOfB = doCpuBoundWorkB(resultOfS1);
string resultOfS2 = await sendToServiceS2Async(resultOfB);
string resultOfC = doCpuBoundWorkC(resultOfS2);
}
我是 async/await 的新手,所以我对它实际发生的事情的理解可能完全错误。
使用 async/await 关键字,编译器将 ProcessData 的代码分解为多个任务。
- Task-A:从 ProcessData 方法的开始直到对 ServiceA 的调用“命中”。
- 任务 B:从我们获取对 ServiceA 的调用结果的那一刻起,一直到对 ServiceB 的调用“成功”为止。
- Task-C:从我们获取对 ServiceB 的调用结果到 ProcessData 方法结束的那一刻。
因此,我们有三个“工作处理单元”而不是单个“处理单元”,其中每个部分都根据其在调度程序队列中的位置安排执行。
问题是,当 Task-B(第一个工作)被放入调度程序的队列时,我可能有数百个 Task-A,由 Parallel.ForEach 放在那里,到 Task-C 时(对于第一个工作)被放在调度程序的队列中,情况会更糟。
我希望数据尽可能快地通过,因此我需要能够优先考虑 Task-C 而不是 Task-B 而不是 Task-A。实现这一目标的最佳方法是什么?
INotifyCompletion, SynchronizationContext 浮现在脑海中,但它似乎是 async/await 的“黑暗角落”。 ParallelExtensionsExtras 具有带有优先级队列的 ReprioritizableTaskScheduler 和 QueuedTaskScheduler,但是我如何告诉 async/await 使用所需的调度程序?
John Skeet 在他的博客中谈到了这个问题:https://codeblog.jonskeet.uk/2010/11/02/configuring-waiting/
【问题讨论】:
标签: c# async-await