【问题标题】:CancellationTokenSource not working properly with Parallel.ForEachCancellationTokenSource 无法与 Parallel.ForEach 一起正常工作
【发布时间】:2016-11-02 20:36:25
【问题描述】:

我有以下代码:

 CancellationTokenSource ts = new CancellationTokenSource(10000);
 ParallelOptions po = new ParallelOptions();
 po.CancellationToken = ts.Token;

 List<int> lItems = new List<int>();

 for (int i = 0; i < 20; i++)
 lItems.Add(i);

 System.Collections.Concurrent.ConcurrentBag<int> lBgs = new System.Collections.Concurrent.ConcurrentBag<int>();

 Stopwatch sp = Stopwatch.StartNew();
 try
 {
     Parallel.ForEach(lItems, po, i =>
     {
         Task.Delay(i * 1000).Wait();
         lBgs.Add(i);
      });
 }
 catch (Exception ex)
 {
 }

Console.WriteLine("Elapsed time: {0:N2} seg     Total items: {1}", sp.ElapsedMilliseconds / 1000.0, lBgs.Count);

我的问题是,如果 CancelationTokenSource 设置为在 10 秒内完成,为什么需要超过 20 秒才能取消操作(并行)

问候

【问题讨论】:

    标签: c# task-parallel-library parallel.foreach


    【解决方案1】:

    没有好的Minimal, Complete, and Verifiable code example,就不可能完全理解你的场景。但根据您发布的代码,您似乎希望您的CancellationToken 影响Parallel.ForEach() 的每个单独迭代的执行。

    但是,它不是这样工作的。 Parallel.ForEach() 方法同时调度各个操作,但是一旦这些操作开始,它们就不受Parallel.ForEach() 方法的控制。如果你希望他们提前终止,你必须自己做。例如:

     Parallel.ForEach(lItems, po, i =>
     {
         Task.Delay(i * 1000, ts.Token).Wait();
         lBgs.Add(i);
      });
    

    就您的代码现在而言,在取消令牌之前,几乎立即启动了所有 20 个操作(有一个短暂的延迟,因为线程池为所有操作创建了足够的线程,如有必要)。也就是说,当您取消令牌时,Parallel.ForEach() 方法不再有办法避免启动操作;他们已经开始了!

    由于您的个人行为不会做任何事情来打扰自己,那么剩下的就是让他们全部完成。启动时间(包括等待线程池创建足够的工作线程)加上最长的总延迟(即开始一个动作的延迟加上该动作的延迟),决定了操作所花费的总时间,以及您的取消令牌没有效果。由于您的最长操作是 20 秒,因此 Parallel.ForEach() 操作的总延迟将始终至少为 20 秒。

    通过我上面显示的更改,每个单独操作的延迟任务将在到期时被您的令牌取消,从而导致任务取消异常。这也会导致操作本身提前终止。

    请注意,将取消令牌分配给ParallelOptions.CancellationToken 属性仍然有价值。即使取消发生得太晚,无法阻止Parallel.ForEach() 启动所有操作,但通过在选项中提供标记,它可以识别每个操作引发的异常是由选项中使用的相同取消标记引起的。这样,它就可以只抛出一个OperationCanceledException,而不是将所有操作异常包装在AggregateException 中。

    【讨论】:

      【解决方案2】:

      我假设你不是真的

      回应

      我的问题是,如果 CancelationTokenSource 设置为在 10 秒内完成,为什么需要超过 20 秒才能取消操作(并行)

      这是因为您没有取消 Parallel.ForEach

      为了真正取消你需要使用

      po.CancellationToken.ThrowIfCancellationRequested();
      

      在 Parallel.ForEach 代码中

      正如前面的回答所指出的,如果你想真正取消由 Task.Delay() 创建的任务,你需要使用 Task.Delay 的重载,它接受一个 CancellationToken

      Task.Delay(i * 1000, po.CancellationToken).Wait();
      

      公共静态任务延迟( 时间跨度延迟, CancellationToken 取消令牌 )

      这里有更多细节

      MSDN How to: Cancel a Parallel.For or ForEach Loop

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-12-14
        • 2020-08-19
        • 2013-02-06
        • 2019-02-02
        • 2021-10-15
        • 2016-07-05
        • 2016-09-13
        相关资源
        最近更新 更多