我需要处理大量数据。当前代码可简化如下:
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。从至少两个角度来看,这种实现并不是最佳的。首先,对服务的调用是阻塞的,所以我们在等待调用返回时使用线程。其次Paralle.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的代码分解为多个任务。 任务-A:从ProcessData方法的开头直到调用ServiceA“点击线”的位置。 任务B:从我们拿起呼叫结果到服务A的那一刻起到呼叫服务B“点击线路”的那一刻。 Task-C:从我们获取ServiceB的调用结果到ProcessData方法结束的那一刻。
因此,我们有三个“处理工作单元”而不是单个“处理工作单元”,其中每个和平根据其在调度程序队列中的位置进行执行。
问题在于,当Task-B(第一项工作)放在调度程序的队列上时,我可能有数百个Task-A,由Parallel.ForEach
放在那里,并且当时任务-C(对于第一项工作)放在调度程序的队列中,情况会更糟。
我希望数据尽可能快地通过,因此我需要能够通过Task-A优先于Task-B上的Task-C。实现这一目标的最佳途径是什么?
想到了{p>INotifyCompletion
,SynchronizationContext
,但它似乎是async / await的“黑暗角落”。 ParallelExtensionsExtras具有ReprioritizableTaskScheduler
和QueuedTaskScheduler
优先级队列,但是如何告诉async / await使用所需的调度程序?
John Skeet在他的博客https://msmvps.com/blogs/jon_skeet/archive/2010/11/02/configuring-waiting.aspx
中谈到了这个问题对不起文章......
答案 0 :(得分:2)
节流可能比优先次序更容易。
我认为TPL Dataflow library最能解决您的问题。它结合了并行和async
技术。
您可以创建“块”并将它们“链接”在一起形成“网格”(在您的情况下,网格是一个管道)。 TransformBlock
可以与同步和异步操作一起使用,也支持parallelism和throttling内置。
或者,您可以使用ProcessData
对SemaphoreSlim
方法应用异步限制(在方法开头调用WaitAsync
,在结尾调用Release
)。但请考虑TPL Dataflow;我发现如果人们做了这么复杂的事情,那么他们通常会发现他们也可以在他们的应用程序的其他部分使用TPL Dataflow。
答案 1 :(得分:0)
你的问题:
线程池每隔500毫秒创建一个额外的线程(如果我没有错误),因为' ProcessData'花费超过500毫秒才能完成,随着时间的推移,我们最终会有数百个线程花费大部分时间等待服务重新开始。
可以修复"通过等待ProcessData并在完成时仅生成新的。 (或者做类似Task.WhenAll(... Task.Delay(500)...,... ProcessData())。
ProcessData内的所有调用都依赖于数据,
string resultOfA = doCpuBoundWorkA(data); string resultOfS1 = await sendToServiceS1Async(resultOfA); string resultOfB = doCpuBoundWorkB(resultOfS1); string resultOfS2 = await sendToServiceS2Async(resultOfB); string resultOfC = doCpuBoundWorkC(resultOfS2);
IIRC,await仅将执行传递给方法" ProcessData"。所以它只能允许其他异步方法运行,但由于数据依赖性,ProcessData内部的调用仍然是背靠背。