CancellationTokenSource与Parallel.ForEach不能正常工作

本文关键字:工作 常工作 Parallel ForEach 不能 CancellationTokenSource | 更新日期: 2023-09-27 18:16:06

我有以下代码:

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

CancellationTokenSource与Parallel.ForEach不能正常工作

如果没有一个好的最小化、完整和可验证的代码示例,就不可能完全理解您的场景。但是根据您发布的代码,您似乎希望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中。

我想你实际上不是

响应

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

发生这种情况是因为您没有取消并行。ForEach

为了真正取消,你需要使用

po.CancellationToken.ThrowIfCancellationRequested();

在并行中。ForEach代码

正如前面的回答所指出的,如果你想实际取消由task . delay()创建的任务,你需要使用task的重载。接收CancellationToken的延迟

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

public static时间间隔延迟,CancellationToken CancellationToken)

更多细节在这里

MSDN如何:取消并行。For或ForEach循环