使用async / await时如何确定await生成的任务的优先级

时间:2014-01-16 15:32:17

标签: c# async-await

我需要处理大量数据。当前代码可简化如下:

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> INotifyCompletionSynchronizationContext,但它似乎是async / await的“黑暗角落”。 ParallelExtensionsExtras具有ReprioritizableTaskSchedulerQueuedTaskScheduler优先级队列,但是如何告诉async / await使用所需的调度程序?

John Skeet在他的博客https://msmvps.com/blogs/jon_skeet/archive/2010/11/02/configuring-waiting.aspx

中谈到了这个问题

对不起文章......

2 个答案:

答案 0 :(得分:2)

节流可能比优先次序更容易。

我认为TPL Dataflow library最能解决您的问题。它结合了并行和async技术。

您可以创建“块”并将它们“链接”在一起形成“网格”(在您的情况下,网格是一个管道)。 TransformBlock可以与同步和异步操作一起使用,也支持parallelismthrottling内置。

或者,您可以使用ProcessDataSemaphoreSlim方法应用异步限制(在方法开头调用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内部的调用仍然是背靠背。