CancellationTokenSource与Parallel.ForEach无法正常工作

时间:2016-11-02 20:36:26

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

我有以下代码:

 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秒来取消操作(并行)?

此致

2 个答案:

答案 0 :(得分:2)

如果没有好的Minimal, Complete, and Verifiable code example,就无法完全理解您的情景。但根据您发布的代码,您似乎希望CancellationToken影响Parallel.ForEach()每次迭代的执行。

但是,这不是它的工作原理。 Parallel.ForEach()方法同时调度各个操作,但是一旦这些操作开始,它们就不受Parallel.ForEach()方法的控制。如果您希望它们提前终止,您必须自己完成。 E.g:

 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中。

答案 1 :(得分:0)

我假设你实际上不是

回应

  

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

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

为了实际取消你需要使用

po.CancellationToken.ThrowIfCancellationRequested();

在Parallel.ForEach代码中

如前所述,如果你想要实际取消Task.Delay()创建的任务,你需要使用Task.Delay的重载,它接受CancellationToken

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

公共静态任务延迟(           TimeSpan延迟,           取消促销取消       )

此处有更多详情

  

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