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


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.