繁体   English   中英

使用 async / await 时如何对 await 生成的任务进行优先级排序

[英]How to prioritize tasks generated by await when using 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。 至少从两个角度来看,这种实施方式并不是最优的。 首先,对服务的调用是阻塞的,所以我们在等待调用返回时阻塞了线程。 其次, 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 的代码分解为多个任务。

  • 任务 A:从 ProcessData 方法的开始到调用 ServiceA 的点“命中电线”。
  • 任务 B:从我们获取对 ServiceA 的调用结果的那一刻起,到对 ServiceB 的调用“接通线路”为止。
  • Task-C:从我们获取调用 ServiceB 的结果的那一刻起,一直到 ProcessData 方法结束。

结果,我们有了三个“工作处理单元”,而不是一个单一的“工作处理单元”,其中每个部分都根据其在调度程序队列中的位置安排执行。

问题是,当 Task-B(对于第一项工作)被放入调度程序的队列时,我可能有数百个 Task-A,由Parallel.ForEach放在那里,到时候 Task-C(对于first piece of work)放在调度器的队列中情况会更糟。

我希望数据尽可能快地通过,所以我需要能够优先考虑任务 C,而不是任务 B,而不是任务 A。 实现这一目标的最佳方式是什么?

INotifyCompletion , SynchronizationContext浮现在脑海中,但它似乎是 async/await 的“暗角”。 ParallelExtensionsExtras 具有带有优先队列的ReprioritizableTaskSchedulerQueuedTaskScheduler ,但我如何告诉 async/await 使用所需的调度程序?

John Skeet 在他的博客中谈到了这个问题: https://codeblog.jonskeet.uk/2010/11/02/configuring-waiting/

节流可能是比确定优先级更容易的方法。

我认为您的问题最好由TPL 数据流库解决。 它结合了并行和async技术。

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

或者,您可以使用SemaphoreSlim将异步节流应用到ProcessData方法(在方法开始时调用WaitAsync ,在方法结束时调用Release )。 但是请考虑 TPL Dataflow; 我发现如果人们正在做这么复杂的事情,那么他们通常会发现他们也可以在他们应用程序的其他部分使用 TPL 数据流。

你的问题:

线程池每 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”之外。 所以只能让其他async方法运行,但是ProcessData内部的调用因为数据依赖还是背靠背的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM