我正在使用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标志,Queue
和AutoResetEvent
使用旧样式方法,但我只是想知道使用{{是否可行1}}和BlockingCollection
我认为像GetConsumingEnumerable()
这样的设施会是。{}
在与使用枚举一起使用时非常有用,因为否则所有管道模式实现示例新内容如Peek
和BlockingCollection
看起来都不耐用,我不得不回到原来的方法。
答案 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 InvalidOperationException
s向您提出,但我会&#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。有了它,您可以将所有处理任务包装到队列的项目中,并简化同步的实现。