C# 使用BlockingCollection<>;:操作取消异常,有更好的方法吗?
我正在使用(坦率地说是很棒的)C# 使用BlockingCollection<>;:操作取消异常,有更好的方法吗?,c#,multithreading,blockingcollection,C#,Multithreading,Blockingcollection,我正在使用(坦率地说是很棒的)BlockingCollection类型来实现一个高度多线程、高性能的应用程序 整个集合有很多吞吐量,在微观层面上,它的性能非常好。但是,对于每个“批次”,它总是通过标记取消令牌来结束。这将导致在任何等待的Take调用上引发异常。这很好,但我会选择返回值或输出参数来发出信号,因为a)异常有明显的开销,b)调试时,我不想手动关闭特定异常的中断异常 实现看起来很紧张,理论上我想我可以分解并重新创建我自己的版本,不使用异常,但也许有一种不那么复杂的方法 我可以在集合中添加
BlockingCollection
类型来实现一个高度多线程、高性能的应用程序
整个集合有很多吞吐量,在微观层面上,它的性能非常好。但是,对于每个“批次”,它总是通过标记取消令牌来结束。这将导致在任何等待的Take
调用上引发异常。这很好,但我会选择返回值或输出参数来发出信号,因为a)异常有明显的开销,b)调试时,我不想手动关闭特定异常的中断异常
实现看起来很紧张,理论上我想我可以分解并重新创建我自己的版本,不使用异常,但也许有一种不那么复杂的方法
我可以在集合中添加一个null
(或者如果没有,则添加一个占位符)对象,以表示进程应该结束,但是还需要有一种方法可以很好地中止,即唤醒等待的线程,并以某种方式告诉它们发生了什么事情
那么-其他收集类型?重建我自己的?有没有办法滥用这个
(一些上下文:我选择了BlockingCollection
,因为它比队列周围的手动锁定有优势。我可以说线程原语的使用非常出色,在我的例子中,几毫秒左右和最佳内核的使用至关重要。)
Edit:我刚刚为这一个打开了一个悬赏窗口。我不相信Anastasiosyal的回答涵盖了我在评论中提出的问题。我知道这是个棘手的问题。有人能提供帮助吗?正如我猜您自己已经做过的那样,查看BlockingCollection的反射源,很不幸,当一个CancellationToken被传递到BlockingCollection并被取消时,您将得到OperationCancelled异常,如下图所示(图片后面有几个变通方法)
getconsumineGenumerable
对BlockingCollection调用TryTakeWithNoTimeValidation
,这反过来会引发此异常
解决办法#1
一种可能的策略是,假设您对生产者和消费者有更多的控制权,而不是将取消令牌传递到BlockingCollection(这将引发此异常),您将取消令牌传递到生产者和消费者
如果您的生产者没有生产,而您的消费者没有消费,则您已在不引发此异常的情况下,通过在BlockingCollection中传递CancellationToken.None,有效地取消了操作
特殊情况当BlockingCollection处于BoundedCapacity或为空时取消
生产者被阻止:当达到BlockingCollection上的BoundedCapacity时,生产者线程将被阻止。因此,当尝试取消且BlockingCollection处于BoundedCapacity时(这意味着您的消费者未被阻止,但生产者被阻止,因为他们无法向队列添加任何其他项目)然后,您需要允许消费额外的项目(每个生产者线程一个),这将取消阻止生产者(因为它们在添加到blockingCollection时被阻止),并反过来允许取消逻辑在生产者端生效
已阻止的使用者:当您的使用者因队列为空而被阻止时,您可以在阻止集合中插入一个空的工作单元(每个使用者线程一个),以便解除对使用者线程的阻止,并允许取消逻辑在使用者端启动
当队列中有项目且未达到限制(如BoundedCapacity或Empty)时,不应阻止生产者和消费者线程
解决办法#2
使用一个工作单元
当您的应用程序需要取消时,您的生产者(可能只有一个生产者就足够了,而其他生产者只是取消生产)将生成一个取消工作单元(可以是空的,正如您所提到的,或者是实现标记接口的某个类)。当使用者使用此工作单元并检测到它实际上是一个取消工作单元时,其取消逻辑将生效。要生成的取消工作单元的数量需要等于使用者线程的数量
同样,当我们接近边界容量时,需要谨慎,因为这可能是一些生产商被封锁的迹象。根据生产商/消费者的数量,你可以让消费者消费,直到所有生产商(1除外)已关闭。这可确保周围没有延迟的制作人。当只剩下一个制作人时,您的最后一个消费者可以关闭,制作人可以停止生产取消工作单元。我刚才做的封锁队列如何
它应该可以正常运行,没有任何异常。当前队列只是关闭dispose上的事件,这可能不是您想要的。您可能需要设置一个null,并等待所有项目都得到处理。除此之外,它应该适合您的需要
using System.Collections.Generic;
using System.Collections;
using System.Threading;
using System;
namespace ApiChange.Infrastructure
{
/// <summary>
/// A blocking queue which supports end markers to signal that no more work is left by inserting
/// a null reference. This constrains the queue to reference types only.
/// </summary>
/// <typeparam name="T"></typeparam>
public class BlockingQueue<T> : IEnumerable<T>, IEnumerable, IDisposable where T : class
{
/// <summary>
/// The queue used to store the elements
/// </summary>
private Queue<T> myQueue = new Queue<T>();
bool myAllItemsProcessed = false;
ManualResetEvent myEmptyEvent = new ManualResetEvent(false);
/// <summary>
/// Deques an element from the queue and returns it.
/// If the queue is empty the thread will block. If the queue is stopped it will immedieately
/// return with null.
/// </summary>
/// <returns>An object of type T</returns>
public T Dequeue()
{
if (myAllItemsProcessed)
return null;
lock (myQueue)
{
while (myQueue.Count == 0)
{
if(!Monitor.Wait(myQueue, 45))
{
// dispatch any work which is not done yet
if( myQueue.Count > 0 )
continue;
}
// finito
if (myAllItemsProcessed)
{
return null;
}
}
T result = myQueue.Dequeue();
if (result == null)
{
myAllItemsProcessed = true;
myEmptyEvent.Set();
}
return result;
}
}
/// <summary>
/// Releases the waiters by enqueuing a null reference which causes all waiters to be released.
/// The will then get a null reference as queued element to signal that they should terminate.
/// </summary>
public void ReleaseWaiters()
{
Enqueue(null);
}
/// <summary>
/// Waits the until empty. This does not mean that all items are already process. Only that
/// the queue contains no more pending work.
/// </summary>
public void WaitUntilEmpty()
{
myEmptyEvent.WaitOne();
}
/// <summary>
/// Adds an element of type T to the queue.
/// The consumer thread is notified (if waiting)
/// </summary>
/// <param name="data_in">An object of type T</param>
public void Enqueue(T data_in)
{
lock (myQueue)
{
myQueue.Enqueue(data_in);
Monitor.PulseAll(myQueue);
}
}
/// <summary>
/// Returns an IEnumerator of Type T for this queue
/// </summary>
/// <returns></returns>
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
while (true)
{
T item = Dequeue();
if (item == null)
break;
else
yield return item;
}
}
/// <summary>
/// Returns a untyped IEnumerator for this queue
/// </summary>
/// <returns></returns>
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable<T>)this).GetEnumerator();
}
#region IDisposable Members
/// <summary>
/// Closes the EmptyEvent WaitHandle.
/// </summary>
public void Dispose()
{
myEmptyEvent.Close();
}
#endregion
}
}
使用System.Collections.Generic;
使用系统集合;
使用系统线程;
使用制度;
命名空间ApiChange.Infrastructure
{
///
///一种阻塞队列,它支持结束标记,以表明插入后不再有剩余工作
///空引用。这将队列限制为仅引用类型。
///
///
公共类阻止队列:IEnumerable,IEnumerable,IDisposable其中T:class
{
///
///用于存储元素的队列
///
专用队列myQueue=新队列();
布尔Myallitemsproced
public void Add(T item){
this.TryAddWithNoTimeValidation(item, -1, new CancellationToken());
}
public void Add(T item, CancellationToken cancellationToken){
this.TryAddWithNoTimeValidation(item, -1, cancellationToken);
}
public bool TryAdd(T item){
return this.TryAddWithNoTimeValidation(item, 0, new CancellationToken());
}
public bool TryAdd(T item, TimeSpan timeout){
BlockingCollection<T>.ValidateTimeout(timeout);
return this.TryAddWithNoTimeValidation(item, (int) timeout.TotalMilliseconds, new CancellationToken());
}
public bool TryAdd(T item, int millisecondsTimeout){
BlockingCollection<T>.ValidateMillisecondsTimeout(millisecondsTimeout);
return this.TryAddWithNoTimeValidation(item, millisecondsTimeout, new CancellationToken());
}
public bool TryAdd(T item, int millisecondsTimeout, CancellationToken cancellationToken){
BlockingCollection<T>.ValidateMillisecondsTimeout(millisecondsTimeout);
return this.TryAddWithNoTimeValidation(item, millisecondsTimeout, cancellationToken);
}