我正在使用TPL支持在C#中实现多线程生产者 - 消费者模式。使用方便的BlockingCollection和Task类,工作很简单,直到我需要处理异常。
众所周知,当BlockingCollection中没有项目供消费时,消费者任务对象将被阻止,并且为了防止这些任务无限期等待,调用BlockingCollection.CompleteAdding()以明确告诉所有人有必要没有更多的项目添加到BlockingCollection并唤醒任何等待的任务。
在我的情况下,可能在多个使用者的启动和BlockingCollection.CompleteAdding()的调用之间,抛出异常。抛出异常时,执行停止,这意味着永远不会调用BlockingCollection.CompleteAdding(),任务将永远等待。
在我的情况下,我有一个大蛋糕,生产者 - 消费者只是其中的一部分,而异常可以从蛋糕的其他部分抛出,这意味着生产者 - 消费者没有简单的方法一块蛋糕来检测异常并处理清理。
是否有任何有用的模式可以遇到这种情况?
答案 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