2013-03-03 5 views
41

나는 다음과 같은 테스트 코드가 : 나는 예외를 포기하지 않는 스레드 방법에서TaskCanceledException이 발생하는 이유는 무엇입니까?

void Button_Click(object sender, RoutedEventArgs e) 
{ 
    var source = new CancellationTokenSource(); 

    var tsk1 = new Task(() => Thread1(source.Token), source.Token); 
    var tsk2 = new Task(() => Thread2(source.Token), source.Token); 

    tsk1.Start(); 
    tsk2.Start(); 

    source.Cancel(); 

    try 
    { 
     Task.WaitAll(new[] {tsk1, tsk2}); 
    } 
    catch (Exception ex) 
    { 
     // here exception is caught 
    } 
} 

void Thread1(CancellationToken token) 
{ 
    Thread.Sleep(2000); 

    // If the following line is enabled, the result is the same. 
    // token.ThrowIfCancellationRequested(); 
} 

void Thread2(CancellationToken token) 
{ 
    Thread.Sleep(3000); 
} 

을,하지만 작업을 시작하는 외부 코드의 try-catch 블록에 TaskCanceledException를 얻을. 왜 이런 일이 일어나고이 경우 token.ThrowIfCancellationRequested();의 목적은 무엇입니까? 스레드 메서드에서 token.ThrowIfCancellationRequested();을 호출하면 예외가 throw되어야한다고 생각합니다.

+0

이 예외는 VS2017 .NET Framework 4.6.2에서 예외를 throw하지 않습니다. –

답변

19

나는 당신이 경쟁 조건의 변형에 달려 있기 때문에 이것이 예상되는 행동이라고 생각합니다. How to: Cancel a task and its children에서

:

강제로 작업을 종료하지 않습니다 호출 스레드; 취소가 요청되었음을 알리는 신호입니다. 작업이 이미 실행 중이면 요청을 확인하고 적절히 응답하는 것은 사용자 대리인의 몫입니다. 작업이 실행되기 전에 취소가 요청 된 경우 사용자 위임은 결코 실행되지 않고 작업 개체는 Canceled 상태로 전환됩니다.

Task Cancellation에서

:

당신은 [...] 단순히 대리자에서 반환하여 작업을 종료 할 수 있습니다. 많은 경우에 충분합니다. 그러나이 "취소 된"타스크 인스턴스는 Canceled 상태가 아닌 RanToCompletion 상태로 전 환됩니다. 여기

내 추측은 당신이 작업에 .Start()를 호출하는 동안, 기회는 당신이 당신의 CancellationTokenSource.Cancel()라고하기 전에 하나 (또는 ​​둘 다) 실제로 시작되지 않았다 점이다. 난 당신이 작업의 시작과 취소 사이에 최소한 3 초 기다려야한다면 예외가 발생하지 않을 것입니다. 또한 두 작업의 .Status 속성을 확인할 수 있습니다. 내가 옳다면, .Status 속성은 예외가 발생했을 때 적어도 하나 이상에서 TaskStatus.Canceled으로 읽어야합니다.

새로운 Task을 시작한다고해서 새 스레드가 만들어지는 것은 아닙니다. 새로운 스레드를 얻는 이유와 단순히 실행을 위해 대기중인 것을 결정하기 위해 TPL로 넘어갑니다.

+0

네, 맞습니다. "새로운 스레드를 얻는 방법을 결정하기 위해 TPL로 넘어갑니다."를 제외하고, 실제로 Start() 메서드는 ThreadPool에 작업을 대기시키고 TPL보다 스택에있는 스레드 풀을 결정합니다. 실제로 작업을 수행 할 수 있습니다. 그러나 작업이 실제로 실행되기 전에 토큰이 취소되면 작업은 여전히 ​​취소됩니다. –

+3

'Task.WaitAll'은 다소 비효율적입니다. 왜냐하면 비동기식 작업을 기다리는 동안 스레드를 차단하기 때문입니다. 대신에'Task.WhenAll'을 호출하면 쓰레드를 차단 해제 할뿐만 아니라 취소 된 작업도 포기하지 않습니다. 그런 다음 Wait() 또는'await' 메서드를 사용하면이 메서드가 throw하는 작업입니다. –

+1

이것은 의도적으로 설계된 것입니다. 시작하기 전에 작업이 취소 된 경우 항상 예외가 throw됩니까? –

관련 문제