2013-03-03 36 views
41

Mam następujący kod testowy:Dlaczego występuje wyjątek 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); 
} 

w metodach gwintu nie rzucać żadnych wyjątków, ale pojawia TaskCanceledException w try-catch bloku zewnętrznego kodu, który uruchamia zadania. Dlaczego tak się dzieje i jaki jest cel tego przypadku w przypadku token.ThrowIfCancellationRequested();. Wierzę, że wyjątek powinien być wyrzucany tylko wtedy, gdy zadzwonię pod numer token.ThrowIfCancellationRequested(); w metodzie wątku.

+0

Nie powoduje to wyjątku w programie VS2017 .NET Framework 4.6.2. –

Odpowiedz

19

Uważam, że jest to oczekiwane zachowanie, ponieważ używasz wariantu wyścigu.

Od How to: Cancel a task and its children:

wywołującego wątku nie przymusowo zakończyć zadanie; sygnalizuje tylko, że żądanie anulowania. Jeśli zadanie jest już uruchomione, to do delegata użytkownika należy powiadomienie o żądaniu i odpowiednia odpowiedź. Jeśli żądanie anulowania jest wymagane przed uruchomieniem zadania, wówczas delegat użytkownika nigdy nie jest wykonywany, a obiekt zadania przechodzi w stan Canceled.

iz Task Cancellation:

można zakończyć operację [...] po prostu powrocie z delegata. W wielu przypadkach jest to wystarczające; Jednak instancja zadania "anulowana" przechodzi w ten sposób w stan RanToCompletion, a nie w stan Canceled.

Moja wykształcone przypuszczenie jest to, że podczas gdy dzwonisz .Start() na swoich dwóch zadań, są szanse, że jeden (lub obaj) nie zacząć zanim nazywa .Cancel() na CancellationTokenSource. Założę się, że jeśli odłożysz przynajmniej trzy sekundy pomiędzy początkiem zadań a anulowaniem, nie wyrzucisz wyjątku. Możesz także sprawdzić właściwość obu zadań. Jeśli mam rację, właściwość powinna przeczytać TaskStatus.Canceled na co najmniej jednym z nich, gdy zostanie zgłoszony wyjątek.

Pamiętaj, że uruchomienie nowego Task nie gwarantuje utworzenia nowego wątku. To TPL decyduje o tym, co dostaje nowy wątek i co jest po prostu czeka w kolejce do wykonania.

+0

Tak, to prawda. Z wyjątkiem tego, że "do TPL należy decyzja o tym, co dostaje nowy wątek", faktycznie metoda Start() w pytaniu natychmiast umieszcza kolejkę w wątku ThreadPool, a pula wątków (która jest niższa w stosie niż TPL) decyduje o tym, kiedy faktycznie wykonać pracę. Ale tak, jeśli token zostanie anulowany przed wykonaniem pracy, zadanie zostanie anulowane. –

+3

'Task.WaitAll' jest nieco zły, ponieważ blokuje wątek podczas oczekiwania na to, co może być pracą asynchroniczną. Jeśli zamiast tego zadzwonisz do 'Task.WhenAll', nie tylko odblokujesz wątek, ale także nie będzie on rzucał anulowanych zadań. Zadanie, które ta metoda wyrzuci, jeśli jednak poczekasz() lub 'zaczekaj' na to. –

+1

Czy jest to zgodne z projektem, zawsze będzie rzutować wyjątek, jeśli zadanie zostało anulowane przed rozpoczęciem? –

Powiązane problemy