任务构造函数中的取消令牌:为什么?

Col*_*lin 214 c# .net-4.0 task-parallel-library cancellation-token

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

CancellationTokenSource source = new CancellationTokenSource();
Task t = new Task (/* method */, source.Token);
Run Code Online (Sandbox Code Playgroud)

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

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

Max*_*kin 246

将此标记传递给Task构造函数会将其与此任务相关联.

引用Stephen Toub在MSDN上的回答:

这有两个主要好处:

  1. 如果令牌在任务开始执行之前已请求取消,则任务将不会执行.它不会转换为Running,而是立即转换为Canceled.这样可以避免运行任务的成本,如果它在运行时只是被取消.
  2. 如果任务的主体也在监视取消令牌并抛出Running包含该令牌(这是ThrowIfCancellationRequested所做的那样),那么当任务看到该OCE时,它会检查OCE的令牌是否与Task的令牌匹配.如果是,则该异常被视为对协作取消的确认,并且任务转换到已取消状态(而不是故障状态).

  • TPL的考虑周到。 (2认同)

use*_*116 28

构造函数在内部使用令牌进行取消处理.如果您的代码想要访问令牌,则您有责任将其传递给自己.我强烈建议您阅读CodePlex上的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();
Run Code Online (Sandbox Code Playgroud)

  • @sergdev:测试后:当你没有将令牌作为参数传递时,myTask.IsCanceled和myTask.Status不相同.状态将失败而不是取消.尽管如此,异常是相同的:在两种情况下都是OperationCanceledException. (3认同)
  • @CobaltBlue:`当 cts.Cancel() 被调用时,任务将被取消并结束,无论你做什么`不。如果任务在它开始之前被取消,它是_Canceled_。如果 Task 的主体从不检查任何标记,它将运行到完成,从而导致 _RanToCompletion_ 状态。如果主体抛出一个`OperationCancelledException`,例如通过`ThrowIfCancellationRequested`,那么Task 将检查该异常的CancellationToken 是否与与该Task 关联的那个相同。如果是,则任务是 _Canceled_。如果没有,那就是_Faulted_。 (3认同)
  • 如果不将令牌作为参数传递怎么办?看起来行为将是相同的,没有目的。 (2认同)
  • @sergdev:您传递令牌以将其注册到任务和调度程序。不传递并使用它将是未定义的行为。 (2认同)
  • 如果我不调用`token.ThrowIfCancellationRequested();`怎么办?在我的测试中,行为是相同的。有任何想法吗? (2认同)

x0n*_*x0n 7

取消并不像许多人想象的那么简单.msdn上的这篇博文中解释了一些细微之处:

例如:

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

http://blogs.msdn.com/b/pfxteam/archive/2009/06/22/9791840.aspx


Eli*_*ron 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;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

*********************************************************************
* 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!!!
Run Code Online (Sandbox Code Playgroud)