18

OK, meine Fragen sind wirklich einfach. Warum wirft dieser Code keine TaskCancelledException?Mit CancellationToken für Timeout in Task.Run funktioniert nicht

static void Main() 
{ 
    var v = Task.Run(() => 
    { 
     Thread.Sleep(1000); 
     return 10; 
    }, new CancellationTokenSource(500).Token).Result; 

    Console.WriteLine(v); // this outputs 10 - instead of throwing error. 
    Console.Read(); 
} 

Aber dieses Werk

static void Main() 
{ 
    var v = Task.Run(() => 
    { 
     Thread.Sleep(1000); 
     return 10; 
    }, new CancellationToken(true).Token).Result; 

    Console.WriteLine(v); // this one throws 
    Console.Read(); 
} 

Antwort

24

Cancellation in Managed Threads:

Stornierung kooperativ ist und nicht auf den Hörer gezwungen. Der Hörer bestimmt, wie er als Antwort auf eine Löschanforderung ordnungsgemäß beendet wird.

Du hast schreibt keinen Code in Ihrer Task.Run Methode für den Zugriff auf Ihrem CancellationToken und Löschung zu implementieren - so dass Sie effektiv den Antrag auf Löschung ignoriert und liefen bis zur Fertigstellung.

+0

siehe aktualisierte Frage - Wenn es kooperativ ist, warum funktioniert der zweite? – Aliostad

+0

Die CancellationTokenSource wird mit einem Timeout eingerichtet. Wenn Task.Delay anstelle von Thread.Sleep verwendet wird, kann die Ausnahme ausgelöst werden. –

+4

@Aliostad - 'Tasks 'warten möglicherweise darauf, für eine bestimmte Zeit geplant zu werden, bevor ihr Code tatsächlich ausgeführt wird. Eine offensichtliche Optimierung für eine Aufgabe, die mit einem Abbruch-Token versehen wurde, besteht darin, dieses Token zu überprüfen, bevor Sie die Aufgabe tatsächlich für einen Thread planen. Im zweiten Fall, wenn diese Prüfung durchgeführt wird, wurde die Stornierung bereits angefordert, so dass die Aufgabe nie wirklich gestartet wird. –

12

Ich denke, weil Sie nicht die Methode ThrowIfCancellationRequested() von Ihrem CancellationToken-Objekt aufrufen. Sie ignorieren auf diese Weise die Anforderung zum Abbruch der Aufgabe.

Sie sollten etwas tun:

void Main() 
{ 
    var ct = new CancellationTokenSource(500).Token; 
    var v = 
    Task.Run(() => 
    { 
     Thread.Sleep(1000); 
     ct.ThrowIfCancellationRequested(); 
     return 10; 
    }, ct).Result; 

    Console.WriteLine(v); //now a TaskCanceledException is thrown. 
    Console.Read(); 
} 

Die zweite Variante des Codes funktioniert, weil Sie bereits ein Token mit einem Canceled Zustand auf true gesetzt initialisiert. Tatsächlich ist, wie here erklärt:

If canceled is true, both CanBeCanceled and IsCancellationRequested will be true 

die Stornierung bereits angefordert wurde, und dann die Ausnahme TaskCanceledException sofort geworfen werden, ohne die Aufgabe tatsächlich zu starten.

+0

siehe aktualisierte Frage – Aliostad

+0

@Aliostad Ich aktualisierte die Antwort. –

+0

Aber was ist der Sinn der Weitergabe von ct an Task.Run? – Zhiliang

10

Es gibt einen Unterschied beim Abbrechen einer laufenden Aufgabe und bei einer Aufgabe, deren Ausführung geplant ist.

Nach dem Aufruf der Task.Run-Methode wird die Aufgabe nur geplant und wahrscheinlich noch nicht ausgeführt.

Wenn Sie die Task.Run (..., CancellationToken) -Familie von Überladungen mit Abbruchunterstützung verwenden, wird das Abbruch-Token überprüft, wenn die Aufgabe ausgeführt wird. Wenn das Storno-Token IsCancellationRequested zu diesem Zeitpunkt auf true gesetzt hat, wird eine Ausnahme vom Typ TaskCanceledException ausgelöst.

Wenn die Aufgabe bereits ausgeführt wird, muss die Task die ThrowIfCancellationRequested-Methode aufrufen oder einfach die OperationCanceledException auslösen.

Laut MSDN, es ist nur eine bequeme Methode für die folgenden:

if (Token.IsCancellationRequested) Wirf neue OperationCanceledException (Token);

Nicht die andere Art von Ausnahme in diesen beiden Fällen verwendet:

catch (TaskCanceledException ex) 
{ 
    // Task was canceled before running. 
} 
catch (OperationCanceledException ex) 
{ 
    // Task was canceled while running. 
} 

Beachten Sie auch, dass TaskCanceledException von OperationCanceledException ableitet, so können Sie nur eine catch-Klausel für die OperationCanceledException Typ:

catch (OperationCanceledException ex) 
{ 
    if (ex is TaskCanceledException) 
     // Task was canceled before running. 
    // Task was canceled while running. 
}