C# 并行取消单个任务或特定任务。c中的ForEach#

C# 并行取消单个任务或特定任务。c中的ForEach#,c#,task,parallel.foreach,C#,Task,Parallel.foreach,我需要使用Parallel.ForEach运行一些任务。用户有时可以决定取消提供给Parallel.ForEach的任务列表中的任何任务或单个任务。 我已尝试使用CancellationToken,但它会激发任务列表中的所有任务。我也尝试过并行状态,但即使这样也无法实现我所期望的 我使用下面的代码处理这个POC,但我无法使它工作 瓶颈在下面的语句中 Parallel.ForEach(nums, new ParallelOptions() { CancellationToken = new Can

我需要使用Parallel.ForEach运行一些任务。用户有时可以决定取消提供给Parallel.ForEach的任务列表中的任何任务或单个任务。 我已尝试使用CancellationToken,但它会激发任务列表中的所有任务。我也尝试过并行状态,但即使这样也无法实现我所期望的

我使用下面的代码处理这个POC,但我无法使它工作

瓶颈在下面的语句中

Parallel.ForEach(nums, new ParallelOptions() { CancellationToken = new CancellationToken() } , (num) =>
因为我们必须在Foreach中传递取消令牌,所以我无法为Foreach循环中的每个项目传递单独的取消令牌

我不想采用这种任务方法,因为任务与并行之间存在开销

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;

class Program
{
    static void Main(string[] args)
    {
        int[] nums = Enumerable.Range(0, 10000000).ToArray();
        CancellationTokenSource cts = new CancellationTokenSource();

        // Use ParallelOptions instance to store the CancellationToken
        ParallelOptions po = new ParallelOptions();
        po.CancellationToken = cts.Token;
        po.MaxDegreeOfParallelism = System.Environment.ProcessorCount;
        Console.WriteLine("Press any key to start. Press 'c' to cancel.");
        Console.ReadKey();

        // Run a task so that we can cancel from another thread.
        Task.Factory.StartNew(() =>
        {
            if (Console.ReadKey().KeyChar == 'c')
                cts.Cancel();

            Console.WriteLine("press any key to exit");
        });

        try
        {   
            Parallel.ForEach(nums, new ParallelOptions() { CancellationToken = new CancellationToken() } , (num) =>
            {
                double d = Math.Sqrt(num);
                Console.WriteLine("{0} on {1}", d, Thread.CurrentThread.ManagedThreadId);
                po.CancellationToken.ThrowIfCancellationRequested();
            });
        }
        catch (OperationCanceledException e)
        {
            Console.WriteLine(e.Message);
        }
        finally
        {
            cts.Dispose();
        }

        Console.ReadKey();
}

如果我正确理解了您的问题,您应该能够为每个任务(或任务组,无论标准是什么)创建单独的取消令牌。在执行任务的昂贵部分之前,可以通过相应的令牌显式调用
throwifcancellationrequest

// Assume the nums.length == tokens.length
int[] nums = Enumerable.Range(0, 10000000).ToArray();
CancellationToken[] tokens = ...

try
{
    Parallel.ForEach(nums, (num) =>
    {
        tokens[num].ThrowIfCancellationRequested();
        double d = Math.Sqrt(num);
        Console.WriteLine("{0} on {1}", d, Thread.CurrentThread.ManagedThreadId);        
    });
}
catch (AggregateException e)
{
    // One or more tasks was canceled or threw an exception.
    // e.InnerExceptions contains the individual OperationCanceledException
    // or other exceptions that were thrown.
}
请注意检查任务中的令牌与向
Parallel.ForEach
提供令牌之间的区别。取消整个
并行。ForEach
应避免运行任何尚未启动的任务,并引发单个OperationCanceledException

ParallelOptions po = new ParallelOptions();
po.CancellationToken = cts.Token;
// ...

try
{
    Parallel.ForEach(nums, po, (num) => { ... });
}
catch (OperationCanceledException e)
{
    // The entire operation was cancelled.
}

而不是并行。因为您可以在项目中使用foreach项:
Task.Factory.StartNew
Task.Run
,然后您可以取消单个任务。您可以添加更多关于任务开销的信息吗?我不理解您问题的这一部分:“[在任何时候]用户可以从提供给Parallel.ForEach的任务列表中决定取消任何任务或单个任务”-用户如何决定取消哪个任务?您的示例只有一个取消令牌。任务几乎是82。每个任务的运行时间为20秒到20分钟。每个任务都有一个testname。我们希望用户向我们提供一个TestName,我们应该能够取消该任务。我喜欢您的解决方案,但它有点容易受到外部更改的影响。依我看,我们应该始终尝试将状态对象(如令牌)传递到任务中。当前,您正在从任务内部访问外部可变集合对象
令牌
。如果令牌[]被修改或设置为Null,该怎么办?我想您可以使用一个不可变的令牌集合。这取决于你的要求。您不需要预先为每个任务分配令牌-这实际上相当昂贵。您可以使用一些其他逻辑来检查取消并显式抛出
OperationCanceledException