簡體   English   中英

任務構造函數中的取消標記:為什么?

[英]Cancellation token in Task constructor: why?

某些System.Threading.Tasks.Task構造函數將CancellationToken作為參數:

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

對此讓我感到困惑的是,沒有辦法從方法主體內部實際獲取傳入的令牌(例如,沒有像Task.CurrentTask.CancellationToken那樣的Task.CurrentTask.CancellationToken )。 令牌必須通過一些其他機制提供,例如狀態對象或在 lambda 中捕獲。

那么在構造函數中提供取消令牌有什么作用呢?

CancellationToken傳遞到Task構造函數將其與任務相關聯。

從 MSDN引用Stephen Toub 的回答

這有兩個主要好處:

  1. 如果令牌在Task開始執行之前請求取消,則Task將不會執行。 它不會轉換到Running ,而是立即轉換到Canceled 這避免了運行任務的成本,如果它無論如何都會在運行時被取消。
  2. 如果任務的主體也在監視取消標記並拋出一個包含該標記的OperationCanceledException (這就是ThrowIfCancellationRequested所做的),那么當任務看到OperationCanceledException ,它會檢查OperationCanceledException的標記是否與任務的標記匹配。 如果是,則該異常被視為對協作取消的確認,並且Task轉換到Canceled狀態(而不是Faulted狀態)。

構造函數在內部使用令牌進行取消處理。 如果您的代碼想要訪問令牌,您有責任將其傳遞給自己。 我強烈建議您閱讀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();

取消並不像許多人想象的那樣簡單。 在 msdn 上的這篇博文中解釋了一些微妙之處:

例如:

在 Parallel Extensions 和其他系統中的某些情況下,有必要出於非用戶明確取消的原因喚醒被阻止的方法。 例如,如果一個線程由於集合為空而在blockingCollection.Take()上被阻塞,而另一個線程隨后調用了blockingCollection.CompleteAdding() ,那么第一個調用應該被喚醒並拋出一個InvalidOperationException以表示不正確的用法。

並行擴展中的取消

這是一個代碼示例,演示了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!!!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM