3

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

    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 将继续工作,因为它已经在处理中。

在任务 1 中,它调用方法 MainTaskRoutine,这反过来又创建了更多任务。

在MainTaskRoutine的函数中我写过:cancellationToken.ThrowIfCancellationRequested();

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

4

2 回答 2

4

[已编辑]当您在代码中使用数组时,我假设可能有多个任务,而不仅仅是一个。我还假设在您开始的每个任务中,您首先Process要执行一些 CPU 密集型工作(//do some work here),然后运行MainTaskRoutine​​.

您如何处理任务取消异常取决于您的项目设计工作流程。例如,您可以在Process方法内部或从您调用的地方执行此操作Process。如果您唯一关心的是从跟踪待处理任务的数组中删除 Task 对象,则可以使用Task.ContinueWith来完成。无论任务的完成状态如何(或) Cancelled,都将执行延续:FaultedRanToCompletion

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);
}

您的MainTaskRoutine结构可以与完全相同,并且具有相同的方法Process签名(返回 a Task)。

然后您可能希望对返回的聚合任务执行阻塞等待Process,或异步处理其完成,例如:

// 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);
    }
}
于 2013-09-23T01:52:48.723 回答
1

在做了一些研究后,我发现了这个链接

代码现在看起来像这样: 请参阅下面代码中 CancellationTokenSource.CreateLinkedTokenSource 的用法

    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
            } 
        }
    }

注意:我没有使用它,但一旦完成我会告诉你:)

于 2013-09-24T16:04:22.690 回答