C# 取消令牌源和嵌套任务

C# 取消令牌源和嵌套任务,c#,.net,multithreading,task-parallel-library,task,C#,.net,Multithreading,Task Parallel Library,Task,我对我正在使用的取消令牌源有疑问,如下代码所示: void Process() { //for the sake of simplicity I am taking 1, in original implementation it is more than 1 var cancellationToken = _cancellationTokenSource.Token; Task[] tArray = new Task[1];

我对我正在使用的取消令牌源有疑问,如下代码所示:

    void Process()
    {
        //for the sake of simplicity I am taking 1, in original implementation it is more than 1
        var cancellationToken = _cancellationTokenSource.Token;
        Task[] tArray = new Task[1];
        tArray[0] = Task.Factory.StartNew(() =>
        {
            cancellationToken.ThrowIfCancellationRequested();
            //do some work here
            MainTaskRoutine();
        }, cancellationToken);

        try
        {
            Task.WaitAll(tArray);
        }
        catch (Exception ex)
        {
            //do error handling here
        }
    }

    void MainTaskRoutine()
    {
        //for the sake of simplicity I am taking 1, in original implementation it is more than 1
        //this method shows that a nested task is created 
        var cancellationToken = _cancellationTokenSource.Token;
        Task[] tArray = new Task[1];
        tArray[0] = Task.Factory.StartNew(() =>
        {
            cancellationToken.ThrowIfCancellationRequested();
            //do some work here

        }, cancellationToken);

        try
        {
            Task.WaitAll(tArray);
        }
        catch (Exception ex)
        {
         //do error handling here
        }
    }

编辑:进一步细化

最终目标是:当用户取消操作时,所有立即挂起的任务(子任务或孙子任务)都应取消

场景: 根据上述代码: 1.我首先检查用户是否要求取消 2.如果用户未要求取消,则仅继续执行任务(请参阅处理方法)。 示例代码在这里只显示一个任务,但实际上可以有三个或更多个任务

假设CPU开始处理Task1,而其他任务仍在任务队列中等待一些CPU来执行它们。 用户请求取消:进程方法中的任务2、3将立即取消,但任务1将继续工作,因为它已在进行处理

在Task1中,它调用MainTaskRoutine方法,这反过来会创建更多任务

在MainTaskRoutine的函数中,我编写了:cancellationToken.ThrowIfCancellationRequested()


所以问题是:使用CancellationTokenSource是否正确,因为它依赖于Task.WaitAll()?

[EDITED]当您在代码中使用数组时,我假设可能有多个任务,而不仅仅是一个任务。我还假设,在从
进程开始的每个任务中,您希望首先执行一些CPU限制的工作(
//在此处执行一些工作
),然后运行
MainTaskRoutine

处理任务取消异常的方式由项目设计工作流决定。例如,您可以在
Process
方法内部执行,也可以从调用
Process
的位置执行。如果您唯一关心的是从跟踪挂起任务的数组中删除任务对象,那么可以使用。无论任务的完成状态如何(
Cancelled
Faulted
RanToCompletion
),都将继续执行:


做了一些研究后我发现

代码现在如下所示: 请参阅下面代码中CancellationTokenSource.CreateLinkedTokenSource的用法


注意:我没有使用它,但一旦使用完毕,我会立即通知您:)

注意
Task.Run
将打开一个嵌套的任务,因此从
Task.Run
返回的任务实际上是由
mainstaskroutine
返回的任务。还要注意
TaskContinuationOptions.ExecuteSynchronously
,它用于确保在触发所有
之前执行继续清理代码。我相信我发布的代码运行正常。所有创建的任务共享相同的
cancellationToken
。只要在每个任务中定期调用
cancellationToken.ThrowIfCancellationRequested()
,任何挂起的任务都将被取消<代码>进程
主任务例程
本身不执行任何CPU限制的任务,因此它们将在其所有子任务完成后立即取消(
whalll
)。但是,您可以在
过程的最开始添加
cancellationToken.ThrowIfCancellationRequested()
MainTaskRoutine
,以确保它们不会启动任何子任务。如果我继续将CancellationTokenSource作为参数传递给任务,可以吗,因此,如果任务需要创建任何子任务,它可能会使用此参数?根据您的编辑:最终目标是:当用户取消操作时,所有立即挂起的任务(子任务或子任务)都应取消。我不太明白你说的“立即”是什么意思。您只想取消由
进程创建的任务(而不是由
MainTaskRoutine
)吗?是的,您可以(也应该)将相同的
CancellationTokenSource.Token
传递给任意多个任务,假设调用
CancellationTokenSource时逻辑上应取消所有这些项。对该
CancellationTokenSource
对象取消
。为什么需要新的链接取消源?无论如何,我没有看到你在上面调用
Cancel()
。在我看来,在
CreateLinkedTokenSource
@nosratio中使用至少两个令牌是有意义的。根据我的问题,是用户将取消即时任务,这些任务将触发这些“MainTaskRoutine”任务。总之,它将为所有未决任务产生连锁反应。希望有意义。取消的顺序并不重要,一个原始的取消令牌就足以取消所有任务。
Task Process(CancellationToken cancellationToken)
{
    var tArray = new List<Task>();
    var tArrayLock = new Object();

    var task = Task.Run(() =>
    {
        cancellationToken.ThrowIfCancellationRequested();
        //do some work here

        return MainTaskRoutine(cancellationToken);
    }, cancellationToken);

    // add the task to the array,
    // use lock as we may remove tasks from this array on a different thread
    lock (tArrayLock)
        tArray.Add(task);
    task.ContinueWith((antecedentTask) =>
    {
        if (antecedentTask.IsCanceled || antecedentTask.IsFaulted)
        {
            // handle cancellation or exception inside the task
            // ...
        }
        // remove task from the array,
        // could be on a different thread from the Process's thread, use lock
        lock (tArrayLock)
            tArray.Remove(antecedentTask);
    }, TaskContinuationOptions.ExecuteSynchronously);

    // add more tasks like the above
    // ...

    // Return aggregated task
    Task[] allTasks = null;
    lock (tArrayLock)
        allTasks = tArray.ToArray();
    return Task.WhenAll(allTasks);
}
// handle the completion asynchronously with a blocking wait
void RunProcessSync()
{
    try
    {
        Process(_cancellationTokenSource.Token).Wait();
        MessageBox.Show("Process complete");
    }
    catch (Exception e)
    {
        MessageBox.Show("Process cancelled (or faulted): " + e.Message);
    }
}

// handle the completion asynchronously using ContinueWith
Task RunProcessAync()
{
    return Process(_cancellationTokenSource.Token).ContinueWith((task) =>
    {
        // check task.Status here
        MessageBox.Show("Process complete (or cancelled, or faulted)");
    }, TaskScheduler.FromCurrentSynchronizationContext());
}

// handle the completion asynchronously with async/await
async Task RunProcessAync()
{
    try
    {
        await Process(_cancellationTokenSource.Token);
        MessageBox.Show("Process complete");
    }
    catch (Exception e)
    {
        MessageBox.Show("Process cancelled (or faulted): " + e.Message);
    }
}
    void Process()
    {
        //for the sake of simplicity I am taking 1, in original implementation it is more than 1
        var cancellationToken = _cancellationTokenSource.Token;
        Task[] tArray = new Task[1];
        tArray[0] = Task.Factory.StartNew(() =>
        {
            cancellationToken.ThrowIfCancellationRequested();
            //do some work here
            MainTaskRoutine(cancellationToken);
        }, cancellationToken);

        try
        {
            Task.WaitAll(tArray);
        }
        catch (Exception ex)
        {
            //do error handling here
        }
    }

    void MainTaskRoutine(CancellationToken cancellationToken)
    {
        //for the sake of simplicity I am taking 1, in original implementation it is more than 1
        //this method shows that a nested task is created 

        using (var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
        {
            var cancelToken = cancellationTokenSource.Token;
            Task[] tArray = new Task[1];
            tArray[0] = Task.Factory.StartNew(() =>
            {
                cancelToken.ThrowIfCancellationRequested();
                //do some work here

            }, cancelToken);

            try
            {
                Task.WaitAll(tArray);
            }
            catch (Exception ex)
            {
                //do error handling here
            } 
        }
    }