簡體   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