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

时间:2010-09-14 21:10:32

标签: c# .net-4.0 task-parallel-library cancellation-token

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

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

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

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

4 个答案:

答案 0 :(得分:246)

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

引用Stephen Toub's answer from MSDN

  

这有两个主要好处:

     
      
  1. 如果令牌在任务开始执行之前已请求取消,则任务将不会执行。而不是过渡到   Running,它会立即转换为Canceled。这避免了   如果在运行时只是取消任务,则运行任务的成本   反正。
  2.   
  3. 如果任务正文还监视取消令牌并抛出包含该令牌的OperationCanceledException   (这是ThrowIfCancellationRequested所做的),然后当任务   看到OperationCanceledException,它检查OperationCanceledException的令牌是否与任务的匹配   令牌。如果是,则该异常被视为对此的确认   合作取消和任务过渡到已取消   国家(而不是Faulted州)。
  4.   

答案 1 :(得分:28)

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

本书中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();

答案 2 :(得分:7)

取消并不像许多人想象的那么简单。 msdn:

上的博客文章解释了一些细微之处

例如:

  

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

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

答案 3 :(得分:5)

下面是一个代码示例,演示了accepted answerMax 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!!!