C# 如何取消BlockingCollection上的GetConsumingEnumerable()

C# 如何取消BlockingCollection上的GetConsumingEnumerable(),c#,cancellation,blockingcollection,C#,Cancellation,Blockingcollection,在下面的代码中,我使用CancellationToken在生产者不生产时唤醒GetConsumingEnumerable(),并且我想中断foreach并退出任务。但是我没有看到IsCancellationRequested被记录,我的Task.Wait(超时)等待整个超时时间。我做错了什么 userToken.Task = Task.Factory.StartNew(state => { userToken.CancelToken = new CancellationTokenS

在下面的代码中,我使用CancellationToken在生产者不生产时唤醒GetConsumingEnumerable(),并且我想中断foreach并退出任务。但是我没有看到IsCancellationRequested被记录,我的Task.Wait(超时)等待整个超时时间。我做错了什么

userToken.Task = Task.Factory.StartNew(state =>
{
    userToken.CancelToken = new CancellationTokenSource();

    foreach (var broadcast in userToken.BroadcastQueue.GetConsumingEnumerable(userToken.CancelToken.Token))
    {
        if (userToken.CancelToken.IsCancellationRequested)
        {
            Log.Write("BroadcastQueue IsCancellationRequested");
            break;
            ...
        }
    }

    return 0;
}, "TaskSubscribe", TaskCreationOptions.LongRunning);
后来

UserToken.CancelToken.Cancel();          
try
{
    task.Wait(timeOut);
}
catch (AggregateException ar)
{
    Log.Write("AggregateException " + ar.InnerException, MsgType.InfoMsg);
}
catch (OperationCanceledException)
{
    Log.Write("BroadcastQueue Cancelled", MsgType.InfoMsg);
}

我已经创建了快速原型,它似乎适合我

注意线程。就在令牌取消请求之前休眠(1000)。 您可能正在创建for令牌变量,因为您在不同的线程中创建并访问
项.CancelToken
变量

例如,用于取消任务的代码可能会在错误的(先前的或空的)取消令牌上调用取消

static void Main(string[] args)
{
    CancellationTokenSource token = null;
    BlockingCollection<string> coll = new BlockingCollection<string>();
    var t = Task.Factory.StartNew(state =>
    {
        token = new CancellationTokenSource();
        try
        {
            foreach (var broadcast in coll.GetConsumingEnumerable(token.Token))
            {
                if (token.IsCancellationRequested)
                {
                    return;
                }
            }
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("Cancel");
            return;
        }
    }, "TaskSubscribe", TaskCreationOptions.LongRunning);
    Thread.Sleep(1000);
    token.Cancel();
    t.Wait();
}
static void Main(字符串[]args)
{
CancellationTokenSource token=null;
BlockingCollection coll=新建BlockingCollection();
var t=Task.Factory.StartNew(状态=>
{
令牌=新的CancellationTokenSource();
尝试
{
foreach(coll.getconsumineGenumerable(token.token))中的var广播)
{
if(令牌.IsCancellationRequested)
{
返回;
}
}
}
捕获(操作取消异常)
{
控制台。写入线(“取消”);
返回;
}
},“TaskSubscribe”,TaskCreationOptions.LongRunning);
睡眠(1000);
token.Cancel();
t、 等待();
}

您可以使用
CompleteAdding()
,这表示不再向集合中添加任何项目。如果使用了
getconsumineGenumerable
,foreach将优雅地结束,因为它知道等待更多项目没有意义

基本上,一旦您将项目添加到
BlockingCollection
中,只需执行以下操作:

myBlockingCollection.CompleteAdding() 

使用GetConsumingEnumerable执行foreach循环的所有线程都将停止循环。

您的第一个代码段是从匿名方法运行的吗?我在实现
ThreadStart
委托时遇到过类似的问题。我的解决方案是将本地属性设置为静态(我知道这是一种不好的做法,但它起了作用)。在你的例子中,这似乎不是一个选项,所以我对阅读你的答案感兴趣。我重构了代码,使用了一个函数而不是匿名函数,取消功能正在运行。任务开始现在看起来像这个userToken.Task=Task.Factory.StartNew(状态=>SubscribeTask(userToken,receiveSendEventArgs,request,tokenId),“TaskSubscribe”,TaskCreationOptions.LongRunning);我开始悬赏,以帮助为这项事业找到一个明确的答案。到目前为止,我们都看到的行为毫无意义。希望有人能够详细说明。@Spud在您更改二进制文件行为时不会真正重构。现在将您的匿名方法移动到独立方法。
线程.睡眠
仍然需要吗?如果没有,原因是什么?是的,它仍然是必需的(而且它的代码很臭),要想摆脱它,您需要在创建任务之前将
new CancellationTokenSource()
移动到某个地方。然后它就不能回答问题了。OP能够通过将匿名方法移动到非匿名方法来解决他们的问题。问题是为什么?嗯,那真的很奇怪。对我来说,问题出现的唯一方式就是隐藏的种族条件。您可能通过将取消/等待包装到任务中来掩盖它。通过与并发任务共享同一线程池,这可能会推迟执行(并降低竞争条件的频率)或降低并行度。我花了很多时间寻找BlockingQueue源代码,我觉得它坚如磐石。我会把奖金奖励给你,因为(除了是唯一的答案)你坚持用@Spud解决了这个问题。最后,我们的问题变得大不相同,所以我不太关心的事情是150次。我想是吸取了教训。我正在生产者线程中使用CompleteAdding(),但如果没有添加任何项目,生产者线程将被阻止。消费者线程上的取消令牌超时表示“我已经很长时间没有收到制作人的消息了,所以我要取消我自己”。我不知道这是否是一个坏模式,因为似乎有一个隐含的规则,生产者拥有队列,但我们已经给消费者取消,所以我试图从两端这样做。我知道这是旧的,但这实际上不适用于点网核心。我不确定这是否是一个bug,但这是预期的行为,除非它似乎不起作用