232

某些System.Threading.Tasks.Task构造函数将 aCancellationToken作为参数:

CancellationTokenSource source = new CancellationTokenSource();
Task t = new Task (/* method */, source.Token);

令我感到困惑的是,无法从方法主体内部实际获取传入的令牌(例如,没有类似的Task.CurrentTask.CancellationToken)。令牌必须通过其他机制提供,例如状态对象或在 lambda 中捕获。

那么在构造函数中提供取消令牌的目的是什么?

4

4 回答 4

263

将 a 传递给CancellationToken构造Task函数会将其与任务相关联。

从 MSDN引用Stephen Toub 的回答

这有两个主要好处:

  1. Task如果令牌在开始执行之前已请求取消,Task则不会执行。它不会过渡到 Running,而是立即过渡到Canceled。这避免了运行任务的成本,如果它只是在运行时被取消的话。
  2. 如果任务的主体也在监视取消标记并抛出一个OperationCanceledException包含该标记的标记(这是什么ThrowIfCancellationRequested),那么当任务看到 时OperationCanceledException,它会检查 的标记是否OperationCanceledException与任务的标记匹配。如果是这样,则该异常被视为对合作取消和Task转换到Canceled 状态(而不是Faulted状态)的确认。
于 2010-09-14T21:38:23.220 回答
27

构造函数在内部使用令牌进行取消处理。如果您的代码想要访问令牌,您有责任将其传递给自己。我强烈推荐阅读CodePlex 上的 Parallel Programming with Microsoft .NET 一书

书中 CTS 的示例用法:

CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;

Task myTask = Task.Factory.StartNew(() =>
{
    for (...)
    {
        token.ThrowIfCancellationRequested();

        // Body of for loop.
    }
}, token);

// ... elsewhere ...
cts.Cancel();
于 2010-09-14T21:32:38.453 回答
7

取消并不像许多人想象的那样简单。在 msdn 上的这篇博客文章中解释了一些微妙之处:

例如:

在 Parallel Extensions 和其他系统中的某些情况下,由于非用户明确取消的原因,有必要唤醒阻塞的方法。例如,如果一个线程blockingCollection.Take()由于集合为空而被阻塞,而另一个线程随后调用 blockingCollection.CompleteAdding(),则第一个调用应该唤醒并抛出一个InvalidOperationException以表示不正确的用法。

并行扩展中的取消

于 2010-09-14T21:15:15.117 回答
6

这是一个代码示例,演示了Max Galkin接受的答案中的两点:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("*********************************************************************");
        Console.WriteLine("* Start canceled task, don't pass token to constructor");
        Console.WriteLine("*********************************************************************");
        StartCanceledTaskTest(false);
        Console.WriteLine();

        Console.WriteLine("*********************************************************************");
        Console.WriteLine("* Start canceled task, pass token to constructor");
        Console.WriteLine("*********************************************************************");
        StartCanceledTaskTest(true);
        Console.WriteLine();

        Console.WriteLine("*********************************************************************");
        Console.WriteLine("* Throw if cancellation requested, don't pass token to constructor");
        Console.WriteLine("*********************************************************************");
        ThrowIfCancellationRequestedTest(false);
        Console.WriteLine();

        Console.WriteLine("*********************************************************************");
        Console.WriteLine("* Throw if cancellation requested, pass token to constructor");
        Console.WriteLine("*********************************************************************");
        ThrowIfCancellationRequestedTest(true);
        Console.WriteLine();

        Console.WriteLine();
        Console.WriteLine("Test Completed!!!");
        Console.ReadKey();
    }

    static void StartCanceledTaskTest(bool passTokenToConstructor)
    {
        Console.WriteLine("Creating task");
        CancellationTokenSource tokenSource = new CancellationTokenSource();
        Task task = null;
        if (passTokenToConstructor)
        {
            task = new Task(() => TaskWork(tokenSource.Token, false), tokenSource.Token);

        }
        else
        {
            task = new Task(() => TaskWork(tokenSource.Token, false));
        }

        Console.WriteLine("Canceling task");
        tokenSource.Cancel();

        try
        {
            Console.WriteLine("Starting task");
            task.Start();
            task.Wait();
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception: {0}", ex.Message);
            if (ex.InnerException != null)
            {
                Console.WriteLine("InnerException: {0}", ex.InnerException.Message);
            }
        }

        Console.WriteLine("Task.Status: {0}", task.Status);
    }

    static void ThrowIfCancellationRequestedTest(bool passTokenToConstructor)
    {
        Console.WriteLine("Creating task");
        CancellationTokenSource tokenSource = new CancellationTokenSource();
        Task task = null;
        if (passTokenToConstructor)
        {
            task = new Task(() => TaskWork(tokenSource.Token, true), tokenSource.Token);

        }
        else
        {
            task = new Task(() => TaskWork(tokenSource.Token, true));
        }

        try
        {
            Console.WriteLine("Starting task");
            task.Start();
            Thread.Sleep(100);

            Console.WriteLine("Canceling task");
            tokenSource.Cancel();
            task.Wait();
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception: {0}", ex.Message);
            if (ex.InnerException != null)
            {
                Console.WriteLine("InnerException: {0}", ex.InnerException.Message);
            }
        }

        Console.WriteLine("Task.Status: {0}", task.Status);
    }

    static void TaskWork(CancellationToken token, bool throwException)
    {
        int loopCount = 0;

        while (true)
        {
            loopCount++;
            Console.WriteLine("Task: loop count {0}", loopCount);

            token.WaitHandle.WaitOne(50);
            if (token.IsCancellationRequested)
            {
                Console.WriteLine("Task: cancellation requested");
                if (throwException)
                {
                    token.ThrowIfCancellationRequested();
                }

                break;
            }
        }
    }
}

输出:

*********************************************************************
* Start canceled task, don't pass token to constructor
*********************************************************************
Creating task
Canceling task
Starting task
Task: loop count 1
Task: cancellation requested
Task.Status: RanToCompletion

*********************************************************************
* Start canceled task, pass token to constructor
*********************************************************************
Creating task
Canceling task
Starting task
Exception: Start may not be called on a task that has completed.
Task.Status: Canceled

*********************************************************************
* Throw if cancellation requested, don't pass token to constructor
*********************************************************************
Creating task
Starting task
Task: loop count 1
Task: loop count 2
Canceling task
Task: cancellation requested
Exception: One or more errors occurred.
InnerException: The operation was canceled.
Task.Status: Faulted

*********************************************************************
* Throw if cancellation requested, pass token to constructor
*********************************************************************
Creating task
Starting task
Task: loop count 1
Task: loop count 2
Canceling task
Task: cancellation requested
Exception: One or more errors occurred.
InnerException: A task was canceled.
Task.Status: Canceled


Test Completed!!!
于 2020-02-18T11:35:55.547 回答