2013-03-03 15 views
41

Ho il seguente codice di prova:Perché si verifica 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); 
} 

Nei metodi di filo che non gettare eccezioni, ma ho TaskCanceledException in try-catch blocco del codice esterno che avvia le attività. Perché questo accade e qual è lo scopo di token.ThrowIfCancellationRequested(); in questo caso. Credo che l'eccezione dovrebbe essere generata solo se chiamo token.ThrowIfCancellationRequested(); nel metodo thread.

+0

Questo non genera un'eccezione in VS2017 .NET Framework 4.6.2. –

risposta

19

Credo che questo sia un comportamento previsto perché stai correndo verso una variazione di una condizione di competizione.

Da How to: Cancel a task and its children:

Il thread chiamante non forzatamente terminare l'operazione; segnala solo che è richiesta la cancellazione. Se l'attività è già in esecuzione, è responsabilità dell'utente delegare la richiesta e rispondere in modo appropriato. Se viene richiesta la cancellazione prima dell'esecuzione dell'attività, l'utente delegato non viene mai eseguito e l'oggetto attività passa allo stato Canceled.

e da Task Cancellation:

È possibile terminare l'operazione entro il [...] semplicemente di ritorno dal delegato. In molti scenari questo è sufficiente; tuttavia, un'istanza dell'attività che viene "annullata" in questo modo passa allo stato RanToCompletion, non allo stato Canceled.

mia supposizione istruita qui è che mentre si sta chiamando .Start() sulle due compiti, è probabile che uno (o entrambi) non hanno in realtà iniziare prima chiamato .Cancel() sul CancellationTokenSource. Scommetto che se metti almeno tre secondi di attesa tra l'inizio delle attività e la cancellazione, non farà eccezione. Inoltre, è possibile verificare la proprietà .Status di entrambe le attività. Se ho ragione, la proprietà .Status dovrebbe leggere TaskStatus.Canceled su almeno uno di questi quando viene generata l'eccezione.

Ricordare che l'avvio di un nuovo Task non garantisce la creazione di un nuovo thread. Spetta al TPL decidere cosa ottiene un nuovo thread e cosa viene semplicemente messo in coda per l'esecuzione.

+0

Sì, questo è corretto. Tranne che "rientra nel TPL per decidere cosa ottiene un nuovo thread", in realtà il metodo Start() nella domanda immediatamente accoda l'attività sul ThreadPool, e il pool di thread (che è più basso nello stack rispetto a TPL) decide quando per eseguire effettivamente il lavoro. Ma, se il token viene cancellato prima che il lavoro venga effettivamente eseguito, l'attività verrà comunque annullata. –

+3

'Task.WaitAll' è in qualche modo cattivo, poiché blocca un thread in attesa di quello che potrebbe essere il lavoro asincrono. Se si chiama 'Task.WhenAll' invece, non solo si sbloccherà un thread, ma non genererà nemmeno attività annullate. L'attività che verrà lanciata dal metodo se si aspetta Wait() o "attende" su di essa, comunque. –

+1

Quindi, in base alla progettazione, genera sempre un'eccezione se un'attività è stata annullata prima dell'avvio? –

Problemi correlati