使用C#中的TPL在生产者 - 消费者模式中进行异常处理

时间:2010-09-21 04:40:26

标签: c# multithreading

我正在使用TPL支持在C#中实现多线程生产者 - 消费者模式。使用方便的BlockingCollection和Task类,工作很简单,直到我需要处理异常。

众所周知,当BlockingCollection中没有项目供消费时,消费者任务对象将被阻止,并且为了防止这些任务无限期等待,调用BlockingCollection.CompleteAdding()以明确告诉所有人有必要没有更多的项目添加到BlockingCollection并唤醒任何等待的任务。

在我的情况下,可能在多个使用者的启动和BlockingCollection.CompleteAdding()的调用之间,抛出异常。抛出异常时,执行停止,这意味着永远不会调用BlockingCollection.CompleteAdding(),任务将永远等待。

在我的情况下,我有一个大蛋糕,生产者 - 消费者只是其中的一部分,而异常可以从蛋糕的其他部分抛出,这意味着生产者 - 消费者没有简单的方法一块蛋糕来检测异常并处理清理。

是否有任何有用的模式可以遇到这种情况?

2 个答案:

答案 0 :(得分:2)

我建议您使用调用finally的{​​{1}}块来确保队列始终关闭,即使抛出异常也是如此。

答案 1 :(得分:0)

对于最简单的情况,以下内容就足够了:

producer.ContinueWith(t => consumable.CompleteAdding(),
    TaskContinuationOptions.OnlyOnFaulted);

对于具有较长生产者和消费者链或可能阻塞生产者的上限BlockingCollection的更复杂的情况,这变得棘手,因为任何未处理的异常都可能导致所有链接的任务无限期地阻塞。对我有用的解决方案是使用 BlockingCollection 类的内置取消功能:

var cts = new CancellationTokenSource();
var consumable = new BlockingCollection<T>(10000);
//...
consumable.Add(item, cts.Token);
//...
consumable.GetConsumingEnumerable(cts.Token)

然后,我们只需要确保任何异常都会触发取消:

producerTask.ContinueWith(t => cts.Cancel(), TaskContinuationOptions.OnlyOnFaulted);
intermediateTask.ContinueWith(t => cts.Cancel(), TaskContinuationOptions.OnlyOnFaulted);
consumerTask.ContinueWith(t => cts.Cancel(), TaskContinuationOptions.OnlyOnFaulted);
Task.WhenAll(producerTask, intermediateTask, consumerTask).Wait();
// This way the OperationCanceled exceptions are swallowed