簡體   English   中英

延遲生產者消費者模式

[英]Delayed Producer Consumer pattern

我有一個生產者通過爆發產生整數(在幾秒鍾內產生1到50)。 我有一個消費者按塊使用這些整數。

我希望消費者在制作人完成他的爆發時開始消費(我沒有制作人的領先優勢,我只知道它已經完成生產,當沒有產生任何東西5秒)。

我想到了兩種不同的方式:

第一:使用一種不同於另一種消費者的消費者:

private readonly List<int> _ids = new List<int>();
private readonly ManualResetEvent _mainWaiter = new ManualResetEvent(false);
private readonly ManualResetEvent _secondaryWaiter = new ManualResetEvent(false);

//This methods consumes the id from the producer
public void OnConsumeId(int newId)
{
    lock(_ids)
    {
        _ids.Add(newId);
        _mainWaiter.Set();
        _secondaryWaiter.Set();
    }
}

//This methods runs on the dedicated thread :
public void ConsumerIdByBlock()
{
    while(true)
    {
        _mainWaiter.Wait();
        while(_secondaryWaiter.Wait(5000));

        List<int> localIds;
        lock(_ids)
        {
            localIds = new List<int>(_ids);
            _ids.Clear();
        }
        //Do the job with localIds
    }
}

第二:為最后一次更新提供一種令牌

//This methods consumes the id from the producer
private int _lastToken;
public void OnConsumeId(int newId)
{
    lock(_ids)
    {
        _ids.Add(newId);
        ThreadPool.Queue(()=>ConsumerIdByBlock(++_lastToken));
    }
}

//This methods runs on the dedicated thread :
public void ConsumerIdByBlock(int myToken)
{       
    Thread.Sleep(5000);

    List<int> localIds;
    lock(_ids)
    {
        if(myToken !=_lastToken)
            return;     

        localIds = new List<int>(_ids);
        _ids.Clear();
    }

    //Do the job with localIds  
}

但我發現這些方法有點太復雜了。 是否存在原生/更簡單的解決方案? 你會怎么做?

如果您使用已經具有通知等的線程安全隊列,這將變得容易得多。 BlockingCollection使編寫生產者 - 消費者的東西變得非常容易。

我喜歡你的“鏈接消費者”的想法,因為你不必修改生產者才能使用它。 也就是說,生產者只是把東西塞進隊列中。 消費者最終如何使用它是無關緊要的。 然后,使用BlockingCollection ,您將擁有:

BlockingCollection<ItemType> inputQueue = new BlockingCollection<ItemType>();
BlockingCollection<List<ItemType>> intermediateQueue = new BlockingCollection<List<ItemType>>();

您的生產者通過調用inputQueue.Add將內容添加到輸入隊列。 您的中間消費者(稱之為整合者)通過調用TryTake超時來從隊列中獲取內容。 例如:

List<ItemType> items = new List<ItemType>();
while (!inputQueue.IsCompleted)
{
    ItemType t;
    while (inputQueue.TryTake(out t, TimeSpan.FromSeconds(5))
    {
        items.Add(t);
    }
    if (items.Count > 0)
    {
        // Add this list of items to the intermediate queue
        intermediateQueue.Add(items);
        items = new List<ItemType>();
    }
}

第二個使用者只讀取中間隊列:

foreach (var itemsList in intermediateQueue.GetConsumingEnumerable))
{
    // do something with the items list
}

不需要ManualResetEventlock或其中任何一個; BlockingCollection為您處理所有混亂的並發內容。

為了擴展@Chris的想法,當你使用id時,記住它的時間。 如果自上一個過去超過5秒,則啟動新列表設置事件。 您的塊消費者只需等待該事件並使用存儲的列表。

另請注意,在第一個解決方案中,ConsumerIdByBlock可能在OnConsumeId獲取鎖定之前退出內部,然后ConsumerIdByBlock將消耗至少一個Id太多。

隊列似乎是處理您所描述內容的最佳方式。

不幸的是,線程安全的集合有點用詞不當。 .NET中有“阻塞”集合,但新類的基本原則是不要試圖使基於實例的類“線程安全”(靜態類是一個不同的故事。類級別的“線程安全”是一種做法 - 這個公理硬幣的第二個方面是“沒有任何人”。它不可能針對特定應用的需求或用法進行優化,因此它最終會采取最壞情況並且不能可能會考慮所有應用程序的所有場景,因為它們有時會錯過任何東西。錯過的東西最終需要通過其他方式來覆蓋,並且這兩種方式的交互需要獨立管理。

隊列是一種基本的讀取器/寫入器模式,可以使用稱為讀寫器鎖的鎖定原語進行封裝。 其中有一個名為ReaderWriterLockSlim的類,可用於確保使用隊列集合的應用程序級線程安全性。

我會使用System.Timers.Timer。 設置一個5000ms的間隔,每次產生一個新的id,重啟Timer:

   class Consumer
   {
      List<int> _ids = new List<int>();
      Timer producer_timer = new Timer();

      public Consumer()
      {
         producer_timer.Elapsed += ProducerStopped;
         producer_timer.AutoReset = false;
      }

      public void OnConsumeId(int newId)
      {
         lock (_ids)
         {
            _ids.Add(newId);
            producer_timer.Interval = 5000;
            producer_timer.Start();
         }
      }

      public void ProducerStopped(object o, ElapsedEventArgs e)
      {
         // Do job here.
      }
   }

暫無
暫無

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

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