簡體   English   中英

生產者/消費者,BlockingCollection和等待更改

[英]Producer/Consumer, BlockingCollection, and waiting for changes

我試圖圍繞BlockingCollection和生產者/消費者問題進行討論。

我要實現的目標如下:

  • 各種線程安全隊列,以FIFO方式保存對象列表(“作業”)。
  • 第二個線程安全隊列也以FIFO方式保存這些作業的結果列表。

換一種說法:

Inbound "Job" Data, can come at any time from multiple threads 
   ==> Thread-Safe FIFO Queue 1 "FQ1"
      ==> Async Processing of data in FQ1 (and remove item from FQ1)
         ==> Callback/Results into Thread-Safe FIFO Queue 2 "FQ2"
            ==> Async Processing of data in FQ2 (and remove item from FQ2)
               ==> Done

到目前為止,我的拙劣嘗試是:

private BlockingCollection<InboundObject> fq1;
private BlockingCollection<ResultObject> fq2;

(...)

Task.Factory.StartNew(() =>
{
    foreach (InboundObject a in fq1.GetConsumingEnumerable())
       a.DoWork(result => fq2.Add(result)); //a.DoWork spits out an Action<ResultObject>
}

我選擇BlockingCollection的原因之一是因為我希望將負載保持在最低水平,這意味着僅當項目實際上在集合中時才起作用(而不處理等待/睡眠)。 我不確定foreach是否是正確的方法。

請讓我知道這是否正確或是否有更好的方法。 謝謝!

編輯我可以從單元測試中看出,任務內部的工作實際上是同步的。 新版本如下:

Task.Factory.StartNew(() =>
{
    foreach (InboundObject a in fq1.GetConsumingEnumerable())
       Task.Factory.StartNew(async () => { fq2.Add(await a.DoWork()); });
}

輸入非常感謝!

我選擇BlockingCollection的原因之一是因為我希望將負載保持在最低水平,這意味着僅當項目實際上在集合中時才起作用(而不處理等待/睡眠)。 我不確定foreach是否是正確的方法。

這是正確的方法,在將新項目添加到隊列中或將調用CompleteAdding方法之前, foreach將被阻止。 不正確的是您想使用BlockingCollection實現異步處理。 BlockingCollection是一個簡單的生產者/消費者隊列,需要維護作業和作業結果的順序時必須使用BlockingCollection。 因此,它是同步的。 作業將按照添加時的相同順序進行處理。

如果您需要的只是異步執行,則不需要隊列。 在這種情況下,您可以使用TPL,只需為每個作業生成一個新任務,它們就會由TPL在內部排隊,並且將使用系統可以有效處理的盡可能多的OS線程。 例如,您的工作可以產生自己的任務。 這是更加靈活的方法。

同樣,生產者/消費者隊列可用於組織作業的管道執行。 在這種情況下,必須將作業分為幾個步驟。 每個步驟必須由專用線程執行。 在每個作業步驟線程中,我們必須從一個隊列中讀取作業,執行該作業,然后將其排隊進入下一個隊列。

interface IJob
{
    void Step1();
    void Step2();
    ...
}

var step1 = new BlockingCollection<IJob>();
var step2 = new BlockingCollection<IJob>();
...

Task.Factory.StartNew(() =>
    {
        foreach(var step in step1.GetConsumingEnumerable()) {
            step.Step1();
            step2.Add(step);
        }
    });

Task.Factory.StartNew(() =>
    {
        foreach(var step in step2.GetConsumingEnumerable()) {
            // while performing Step2, another thread can execute Step1
            // of the next job
            step.Step2();
            step3.Add(step);
        }
    });

在這種情況下,作業將以FIFO順序但並行執行。 但是,如果要進行管道處理,則必須首先考慮負載平衡。 如果其中一個步驟花費了太多時間,則它的隊列將變大,而其他線程在大多數情況下將處於空閑狀態。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM