【问题标题】:How to cancel GetConsumingEnumerable() on BlockingCollection如何取消 BlockingCollection 上的 GetConsumingEnumerable()
【发布时间】:2012-02-24 18:12:25
【问题描述】:

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

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);
}

【问题讨论】:

  • 您的第一个 sn-p 是通过匿名方法运行的吗?
  • 我在实现ThreadStart 委托时遇到了类似的问题。我的解决方案是将本地属性设置为静态(我知道这是不好的做法,但它有效)。在您的情况下,这似乎不是一个选项,所以我有兴趣阅读您的答案。
  • 好吧,我重构了代码以使用函数而不是匿名函数,并且取消工作正常。任务开始现在看起来像这样 userToken.Task = Task.Factory.StartNew(state => SubscribeTask(userToken, receiveSendEventArgs, request, tokenId), "TaskSubscribe", TaskCreationOptions.LongRunning);
  • 我开始了一项赏金活动,以帮助为这个原因找到一个明确的答案。到目前为止,我们都看到的行为毫无意义。希望有人能够详细说明。
  • @Spud 并没有真正重构,因为您更改了二进制文件的行为。

标签: c# cancellation blockingcollection


【解决方案1】:

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

基本上,一旦您完成向BlockingCollection 添加项目后,只需执行以下操作:

myBlockingCollection.CompleteAdding() 

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

【讨论】:

  • 我在生产者线程中使用 CompleteAdding() 但如果没有添加任何项目,则生产者线程被阻止。消费者线程上的取消令牌超时表示“我很长时间没有收到生产者的消息,所以我要取消自己”。我不知道这是否是一个不好的模式,因为似乎有一条隐含的规则,即生产者拥有队列,但我们已经取消了消费者,所以我试图从两端都这样做
  • 我知道这是旧的,但这实际上在 dot net core 中不起作用。我不确定这是否是一个错误,但这是预期的行为,除了它似乎没有 wkem
【解决方案2】:

我已经创建了快速原型,它似乎对我有用。

在令牌取消请求之前注意 Thread.Sleep(1000)。 您可能正在为 Token 变量创建 race condition,因为您在不同的线程中创建和访问 item.CancelToken 变量。

例如旨在取消任务的代码可能会在错误(上一个或 null)取消令牌上调用取消。

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();
}

【讨论】:

  • 现在将您的匿名方法移至独立方法。 Thread.Sleep 还需要吗?如果不是,为什么?
  • 是的,它仍然是必要的(而且它是臭代码),要摆脱它,您需要在创建任务之前将new CancellationTokenSource() 移动到某个地方。
  • 那么它没有回答问题。 OP 能够通过将匿名方法移动到非匿名方法来解决他们的问题。问题是为什么
  • 嗯,真奇怪。问题对我造成影响的唯一方式是隐藏的竞争条件。您可能通过将取消/等待包装到任务中来掩盖它。通过与并发任务共享同一个线程池,这可能会推迟执行(并使竞争条件不那么频繁)或降低并行度。我花了很多时间查看 BlockingQueue 源代码,它对我来说似乎坚如磐石。
  • 我会将赏金奖励给您,因为(除了是唯一的答案)您在问题上坚持使用 @Spud。最后,我们的问题最终变得大不相同,所以对于我不太关心的事情来说,这是 150 次代表。我想吸取的教训。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多