【问题标题】:Cancelling BlockingCollection.GetConsumingEnumerable() and processing what's left取消 BlockingCollection.GetConsumingEnumerable() 并处理剩下的
【发布时间】:2013-11-23 14:06:12
【问题描述】:

我有一个进程生成工作,第二个进程使用 BlockingCollection<> 消耗该工作。当我关闭我的程序时,我需要我的消费者停止消费工作,但我仍然需要快速记录等待但尚未消费的工作。

现在,我的消费者产生了一个具有foreach (<object> in BlockingCollection.GetConsumingEnumerable()) 循环的线程。当我停止我的程序时,我的制片人会打电话给Consumer.BlockingCollection.CompleteAdding()。我发现我的消费者继续处理队列中的所有内容。

谷歌搜索问题告诉我我需要使用CancellationToken。所以我试了一下:

private void Process () { // This method runs in a separate thread
    try {
        foreach (*work* in BlockingCollection.GetConsumingEnumerable(CancellationToken)) {
            // Consume
        }
    }
    catch (OperationCancelledException) {
        foreach (*work* in BlockingCollection.GetConsumingEnumerable()) {
            // quickly log
        }
    }
}

我的制作人有:

private CancellationTokenSource StopFlag = new CancellationTokenSource ();
MyConsumer.CancellationToken = StopFlag.Token;
// Make the consumer spawn it's consuming thread...
StopFlag.Cancel ();
MyConsumer.BlockingCollection.CompleteAdding ();

当我尝试这个时,没有任何迹象表明 OperationCancelledException 曾经发生过。

This question 试图解释取消令牌的使用,但似乎没有正确使用它。 (论点:如果它有效,那么它“足够正确”。)this question 似乎与我的问题完全相同,但没有示例。 (同样here。)

所以重申一下:我如何在BlockingCollection.GetConsumingEnumerable() 上正确使用CancellationToken,但需要注意的是,在使用不同的方法取消队列后,我需要处理队列中的剩余项目?

(我认为我的问题集中在正确使用 CancellationToken 上。我的测试均未表明该进程实际上已被取消。(StopFlag.IsCancellationRequested 始终等于 false。))

【问题讨论】:

  • GetConsumingEnumerable 将等待添加更多项目。在 catch 内使用集合本身的 foreach
  • 我很想试试,Romoku!我怎么去呢?我使用了简单的ctor:public BlockingCollection<cOrder> OrderQueue = new BlockingCollection<cOrder> ();
  • 您需要检查取消令牌。 CancellationToken.IsCancellationRequeste 在您的情况下,如果为 true,则仍会处理但记录而不是处理。 msdn.microsoft.com/en-us/library/dd997289.aspx

标签: c# .net multithreading blockingcollection


【解决方案1】:

我的问题在于我是如何尝试取消操作的。我没有让生产者拥有 CancellationTokenSource,而是将其全部放在消费者中。

public class cProducer {
    private cConsumer myConsumer = new cConsumer ();

    public void onStart () {
        myConsumer.OnStart ();
    }

    public void onStop () {
        myConsumer.OnStop ();
    }

    public void OnOrderReceived (cOrder newOrder) {
        myConsumer.orderQueue.Add (cOrder);
    }
}

public class cConsumer {
    private CancellationTokenSource stopFlag;
    public BlockingCollection<cOrder> orderQueue = new BlockingCollection<cOrder> ();
    private Task processingTask;

    public void OnStart () {
        stopFlag = new CancellationTokenSource ();
        processingTask = Task.Factory.StartNew (() => Process ());
    }

    public void OnStop () {
        stopFlag.Cancel ();
        orderQueue.CompleteAdding ();
        processingTask.Wait ();
    }

    private void Process () {
        try {
            foreach (cOrder newOrder in orderQueue.GetConsumingEnumerable (stopFlag.Token)) {
                // process
            }
        }
        catch (OperationCanceledException) {
            foreach (cOrder cancelledOrder in orderQueue.GetConsumingEnumerable ()) {
                // log it
            }
        }
    }
}

【讨论】:

  • 为什么在 OperationCanceledException 处理程序中有 GetConsumingEnumerable 调用?此外,为什么这不阻止 processingTask.Wait() 调用完成。
  • msdn.microsoft.com/en-us/library/dd287086(v=vs.110).aspx 说“在将集合标记为完成添加后,不允许添加到集合中,并且当集合为空时,将不会等待从集合中删除的尝试。”所以我认为现在这是有道理的,但你也可以像在 catch 块中的任何 IEnumerable 一样枚举它。
【解决方案2】:

当您将CancellationToken 传递给GetConsumingEnumerable 时,它不会抛出请求取消的异常,它只会停止吐出项目。与其捕获异常,不如检查令牌:

foreach (var item in BlockingCollection.
    GetConsumingEnumerable(CancellationToken))
{
    //consume item
}
if (CancellationToken.IsCancellationRequested)
    foreach (var item in BlockingCollection)
    {
        //log item
    }

还要注意,如果请求取消,并且可能没有调用 CompletedAdding,那么您应该只迭代集合,而不是调用 GetConsumingEnumerable。如果您知道取消操作时生产者将完成添加,那么这不是问题。

【讨论】:

  • 我在循环外(就像你有它)和循环内都尝试了 if() 语句。没有一个工作。我什至没有收到取消标志正在做任何事情的通知。
  • @Jason 那么你的令牌没有被取消,那是你的问题。确保在您完成项目处理之前确实取消了令牌。
  • @Jason 根据您的使用代码,您在完成添加之前只是取消。很有可能这两个操作将在消费代码再次运行时完成,并且如果您的消费者比您的生产者快(因此它没有剩余物品),它可以很容易地在取消令牌指示之前完成所有物品的消费应该取消。如果您想测试这种情况,您应该使用您的示例,以便消费者不可能在被取消之前正常完成消费。
  • 我是。我有模拟工作的 Sleep() 调用。 “处理”的每个步骤都会记录已处理的项目和队列中剩余的项目数。 (BlockingCollection.Count) 我的原始代码显示计数降至零。随后的测试显示计数只是停止,但我从未收到“操作已取消”的消息
  • GetConsumingEnumerable 将在请求取消时抛出异常
猜你喜欢
  • 2019-02-07
  • 2020-10-04
  • 2021-08-31
  • 2017-08-11
  • 2010-10-12
  • 1970-01-01
  • 2017-10-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多