작업 생성자의 취소 토큰 : 왜?


223

특정 System.Threading.Tasks.Task생성자 CancellationToken는 매개 변수로 사용합니다.

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

이것에 대해 나를 당황스럽게하는 것은 메소드 본문 내부 에서 전달 된 토큰을 실제로 얻을 수있는 방법 이 없다는 것 입니다 (예 :) Task.CurrentTask.CancellationToken. 토큰은 상태 객체와 같은 다른 메커니즘을 통해 제공되거나 람다에서 캡처되어야합니다.

그렇다면 생성자에서 취소 토큰을 제공하는 목적은 무엇입니까?

답변:


254

생성자에 a CancellationToken를 전달 Task하면 작업과 연결됩니다.

MSDN의 Stephen Toub의 답변 인용 :

여기에는 두 가지 주요 이점이 있습니다.

  1. Task실행을 시작하기 전에 토큰이 취소를 요청한 경우 토큰이 실행 Task되지 않습니다. 로 전환하는 대신 Running즉시로 전환됩니다 Canceled. 이렇게하면 작업을 실행하는 동안 작업이 취소되는 경우 작업 실행 비용을 피할 수 있습니다.
  2. 작업 본문에서 취소 토큰도 모니터링하고 OperationCanceledException해당 토큰 이 포함 된 토큰 (이것이 무엇인지 ThrowIfCancellationRequested)을 던지면 작업에서이 토큰을 발견하면의 토큰이 작업 토큰과 일치 OperationCanceledException하는지 확인합니다 OperationCanceledException. 만약 그렇다면, 그 예외는 협동 취소와 국가가 아닌 국가로 의 Task이행에 대한 인정으로 간주된다 .CanceledFaulted

2
TPL은 잘 생각되어 있습니다.
— 대령 패닉

1
나는 이익 1가 취소 토큰을 전달 유사하게 적용 가정 Parallel.For또는Parallel.ForEach
— 대령 패닉

27

생성자는 내부적으로 취소 처리를 위해 토큰을 사용합니다. 코드가 토큰에 액세스하기를 원하는 경우 토큰을 자신에게 전달해야합니다. 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();

3
매개 변수로 토큰을 전달하지 않으면 어떻게됩니까? 행동은 목적이 같지 않은 것처럼 보입니다.
— sergtk

2
@ sergdev : 토큰을 전달하여 작업 및 스케줄러에 등록하십시오. 전달하지 않고 사용하면 정의되지 않은 동작이됩니다.
— user7116

3
@sergdev : 테스트 후 : 토큰을 매개 변수로 전달하지 않으면 myTask.IsCanceled 및 myTask.Status가 동일하지 않습니다. 취소되지 않고 상태가 실패합니다. 그럼에도 불구하고 예외는 동일합니다. 두 경우 모두 OperationCanceledException입니다.
— Olivier de Rivoyre

2
전화하지 않으면 token.ThrowIfCancellationRequested();어떻게됩니까? 내 테스트에서 동작은 동일합니다. 어떤 아이디어?
— machinarium

1
@CobaltBlue : when cts.Cancel() is called the Task is going to get canceled and end, no matter what you do아뇨. 작업이 시작되기 전에 취소 된 경우 취소 됩니다. 작업 본문이 단순히 토큰을 확인하지 않으면 완료로 실행되어 RanToCompletion 상태가됩니다. 본문이 OperationCancelledException예를 들어 by를 던지면 ThrowIfCancellationRequestedTask는 Exception의 CancellationToken이 Task와 연결된 토큰과 같은지 확인합니다. 그렇다면 작업이 취소 됩니다. 그렇지 않으면 Faulted 입니다.
— Wolfzoon

7

많은 사람들이 생각하는 것처럼 취소는 단순한 경우가 아닙니다. msdn에 대한이 블로그 게시물에서 미묘한 부분에 대해 설명합니다.

예를 들면 다음과 같습니다.

Parallel Extensions 및 다른 시스템의 특정 상황에서는 사용자가 명시 적으로 취소하지 않은 이유로 차단 된 방법을 깨워 야합니다. 예를 들어, blockingCollection.Take()컬렉션이 비어 있고 다른 스레드가 이후에 호출하여 하나의 스레드가 차단 된 blockingCollection.CompleteAdding()경우 첫 번째 호출이 일어나서 InvalidOperationException잘못된 사용법을 나타내는를 던져야 합니다.

병렬 확장에서 취소


4

여기에있는 두 점을 보여주는 예입니다 대답 하여 최대 갈킨은 :

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 Done!!!");
        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 Done!!!
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.