在侦听消耗IEnumerable <t> </t>时,Blocking.Peek()的类似于BlockingCollection

时间:2012-11-27 10:16:08

标签: c# multithreading wcf .net-4.0 task-parallel-library

我正在使用Pipelines pattern实现来将消费者与生产者分离,以避免消费者的慢速问题。

如果邮件处理阶段[1]出现任何异常,它将丢失并且不会分派到其他服务/层[2]。如何在[3]中处理此类问题,以便消息不会丢失,重要的是什么!消息的顺序不会混淆,因此上层服务/层将按照它们进入的顺序获取消息。我有一个想法涉及另一个中间Queue,但它看起来很复杂?很遗憾,BlockingCollection<T>没有公开Queue.Peek()方法的任何类似物,所以我只能读取下一个可用消息,如果成功处理,请Dequeue()

private BlockingCollection<IMessage> messagesQueue;    

// TPL Task does following:
// Listen to new messages and as soon as any comes in - process it
foreach (var cachedMessage in 
             messagesQueue.GetConsumingEnumerable(cancellation))
{    
    const int maxRetries = 3;
    int retriesCounter = 0;
    bool isSent = false;

    // On this point a message already is removed from messagesQueue
    while (!isSent && retriesCounter++ <= maxRetries)
    {
        try
        {
           // [1] Preprocess a message
           // [2] Dispatch to an other service/layer    
           clientProxyCallback.SendMessage(cachedMessage);
           isSent = true;
        }                                
        catch(Exception exception)
        {
           // [3]   
           // logging
           if (!isSent && retriesCounter < maxRetries)
           {
              Thread.Sleep(NSeconds);
           }
        }            

        if (!isSent && retriesCounter == maxRetries)
        {
           // just log, message is lost on this stage!
        }
    }
}

编辑:忘了说这是IIS托管的WCF服务,它通过客户端回调契约将邮件发送回Silverlight客户端WCF代理。

EDIT2:以下是我使用Peek()执行此操作的方法,我错过了哪些内容?

bool successfullySent = true;
try
{
   var item = queue.Peek();
   PreProcessItem(item);
   SendItem(item);       
}
catch(Exception exception)
{
   successfullySent = false;
}
finally
{
   if (successfullySent)
   {
       // just remove already sent item from the queue
       queue.Dequeue();
   }
}

EDIT3:当然我可以使用while循环,bool标志,QueueAutoResetEvent使用旧样式方法,但我只是想知道使用{{是否可行1}}和BlockingCollection我认为像GetConsumingEnumerable()这样的设施会是。{} 在与使用枚举一起使用时非常有用,因为否则所有管道模式实现示例新内容如PeekBlockingCollection看起来都不耐用,我不得不回到原来的方法。

4 个答案:

答案 0 :(得分:8)

您应该考虑中间队列。

BlockingCollection<T>因其性质而无法“偷看”商品 - 可能会有多个消费者。其中一个人可以偷看一个项目,而另一个人可以接受它 - 因此,第一个将尝试采取已经采取的项目。

答案 1 :(得分:4)

正如Dennis在评论中所说,BlockingCollection<T>IProducerConsumerCollection<T>接口的任何实现者提供了阻止包装。

如您所见,IProducerConsumerCollection<T>按设计未定义Peek<T>或实现其中所需的其他方法。这意味着BlockingCollection<T>无法按原样为Peek提供分析。

如果您考虑,这可以大大减少公用事业贸易从Peek实施中产生的并发问题。如何在不消费的情况下消费?要同时Peek,您必须锁定集合的头部,直到完成Peek操作,我和BlockingCollection<T>的设计者视为次优。我认为这也很麻烦,难以实施,需要某种一次性的窥视环境。

如果您使用消息并且其消费失败,则必须处理它。您可以将其添加到另一个故障队列,将其重新添加到正常处理队列以进行重新计算,或者只记录其后代故障或适合您的上下文的其他操作。

如果您不想同时使用这些消息,则不需要使用BlockingCollection<T>,因为您不需要同时使用消息。你可以直接使用ConcurrentQueue<T>,你仍然可以获得同步性,并且你可以安全地使用TryPeek<T>,因为你控制了一个消费者。如果消费失败,您可以通过无限重试循环停止消费,但我建议这需要一些设计思路。

答案 2 :(得分:3)

BlockingCollection<T>IProducerConsumerCollection<T>的封套,比{1}更通用。 ConcurrentQueue并为实施者提供了不必实施(Try)Peek方法的自由。

但是,您始终可以直接在基础队列上调用TryPeek

ConcurrentQueue<T> useOnlyForPeeking = new ConcurrentQueue<T>();
BlockingCollection<T> blockingCollection = new BlockingCollection<T>(useOnlyForPeeking);
...
useOnlyForPeeking.TryPeek(...)

但请注意,您不得通过useOnlyForPeeking 修改您的队列,否则blockingCollection会感到困惑,可能会throw InvalidOperationExceptions向您提出,但我会&#39;}如果在此并发数据结构上调用非修改TryPeek将是一个问题,我会感到惊讶。

答案 3 :(得分:0)

您可以改用 ConcurrentQueue<T> ,它有TryDequeue()方法。

  

ConcurrentQueue<T>.TryDequeue(out T result)尝试在并发队列的开头删除并返回对象,如果从头开始删除并返回,则返回 true ConcurrentQueue成功。

所以,不需要先查看Peek。

TryDequeue()是线程安全的:

  

ConcurrentQueue<T> 在内部处理所有同步。如果两个线程在同一时刻调用TryDequeue(T),则两个操作都不会被阻止。

据我所知,仅当队列为空时才返回false

  

如果队列中填充了q.Enqueue(&#34; a&#34;)等代码; q.Enqueue(&#34; B&#34); q.Enqueue(&#34; C&#34);并且两个线程同时尝试使一个元素出列,一个线程将使一个线程出列,另一个线程将出列b。对TryDequeue(T)的两次调用都将返回true,因为它们都能够使元素出列。如果每个线程返回以使其他元素出列,则其中一个线程将使队列出队并返回true,而另一个线程将查找队列为空并返回false。

http://msdn.microsoft.com/en-us/library/dd287208%28v=vs.100%29.aspx

<强>更新

也许,最简单的选择是使用TaskScheduler Class。有了它,您可以将所有处理任务包装到队列的项目中,并简化同步的实现。