Warum wird diese Ausnahme nicht ausgelöst?

8

Ich benutze manchmal eine Reihe von Aufgaben, und um sicherzustellen, dass sie alle erwartet werden, verwende ich diesen Ansatz:

%Vor%

und nenne es dann so:

%Vor%

In letzter Zeit habe ich jedoch merkwürdiges Verhalten bemerkt und festgestellt, ob es ein Problem mit der ReleaseAsync-Methode gab. Ich konnte es auf diese einfache Demo eingrenzen, es läuft in linqpad, wenn du System.Threading.Tasks eingibst. Es funktioniert auch leicht modifiziert in einer Konsolen-App oder in einem asp.net mvc Controller.

%Vor%

Was ich nicht verstehe ist, warum die Exception niemals irgendwo auftaucht. Ich hätte gedacht, dass wenn die Aufgaben mit der Ausnahme erwartet würden, würde es werfen. Ich konnte dies bestätigen, indem ich die while-Schleife durch eine einfache für jeden wie folgt ersetzen:

%Vor%

Meine Frage ist, warum das gezeigte Beispiel die Exception nicht richtig ausgibt (sie erscheint nie irgendwo).

    
Travis J 20.05.2014, 18:00
quelle

3 Antworten

9

TL; DR : run() löst die Ausnahme aus, aber Sie warten auf WhenAny() , das selbst keine Ausnahme auslöst.

Die MSDN-Dokumentation für WhenAny lautet:

  

Die zurückgegebene Aufgabe wird abgeschlossen, wenn eine der bereitgestellten Aufgaben abgeschlossen wurde. Die zurückgegebene Aufgabe endet immer im Status RanToCompletion , wobei als Ergebnis die erste abgeschlossene Aufgabe festgelegt wird. Dies gilt auch dann, wenn die erste abgeschlossene Aufgabe im Status Cancelled oder Faulted endet.

Im Wesentlichen passiert Folgendes: Die von WhenAny zurückgegebene Aufgabe schluckt einfach die fehlerhafte Aufgabe. Es ist nur wichtig, dass die Aufgabe beendet ist, nicht dass sie erfolgreich abgeschlossen wurde. Wenn Sie auf die Aufgabe warten, wird sie einfach ohne Fehler abgeschlossen, da die interne Aufgabe fehlerbehaftet ist und nicht die, auf die Sie warten.

    
Kendall Frey 20.05.2014, 18:12
quelle
10

A Task ist nicht awaited oder verwendet nicht die Methode Wait() oder Result() , schluckt die Ausnahme standardmäßig. Dieses Verhalten kann zurück zu der Art und Weise geändert werden, wie es in .NET 4.0 getan wurde, indem der laufende Prozess abstürzte, nachdem das Task GC wurde. Du kannst es in deinem app.config wie folgt einstellen:

%Vor%

Ein Zitat aus Dies ist ein Blogbeitrag des Teams für parallele Programmierung in Microsoft:

  

Diejenigen von Ihnen, die mit Aufgaben in .NET 4 vertraut sind, werden wissen, dass die TPL den Begriff "unbeobachtete" Ausnahmen hat. Dies ist ein Kompromiss zwischen zwei konkurrierenden Entwurfszielen in TPL: Er unterstützt das Marshalling nicht behandelter Ausnahmen von der asynchronen Operation zu dem Code, der seine Vervollständigung / Ausgabe verbraucht, und die Standard-Exception-Eskalationsrichtlinien für Ausnahmen, die nicht vom Anwendungscode behandelt werden. Seit .NET 2.0 führen Ausnahmen, die in neu erstellten Threads und in ThreadPool-Arbeitselementen nicht behandelt werden, zu dem standardmäßigen Ausnahmeeskalationsverhalten, das zum Absturz des Prozesses führt. Dies ist in der Regel wünschenswert, da Ausnahmen anzeigen, dass etwas schief gelaufen ist. Ein Absturz hilft Entwicklern, sofort zu erkennen, dass die Anwendung in einen unzuverlässigen Zustand übergegangen ist. Im Idealfall würden Aufgaben demselben Verhalten folgen. Aufgaben werden jedoch zum Darstellen von asynchronen Operationen verwendet, zu denen später Code hinzugefügt wird. Wenn diese asynchronen Vorgänge zu Ausnahmen führen, sollten diese Ausnahmen an den Speicherort des Verbindungscodes gemarshallt werden und die Ergebnisse der asynchronen Operation verbrauchen. Das bedeutet inhärent, dass TPL diese Ausnahmen abfangen und an ihnen festhalten muss, bis sie wieder ausgelöst werden können, wenn der konsumierende Code auf die Aufgabe zugreift. Da dies die Standard-Eskalationsrichtlinie verhindert, wandte .NET 4 den Begriff "unbeobachtete" Ausnahmen an, um den Begriff "nicht behandelte" Ausnahmen zu ergänzen. Eine "unbeobachtete" Ausnahme ist eine, die in der Aufgabe gespeichert ist, aber nie von dem konsumierenden Code betrachtet wurde. Es gibt viele Möglichkeiten, die Ausnahme zu beobachten, z. B. Wait () in der Task, Zugriff auf das Ergebnis einer Task, Blick auf die Exception-Eigenschaft der Task und so weiter. Wenn der Code niemals die Ausnahme einer Task beobachtet, wird die TaskScheduler.UnobservedTaskException ausgelöst, wenn die Task nicht mehr aktiv ist. Dadurch erhält die Anwendung eine weitere Möglichkeit, die Ausnahme zu "beobachten". Wenn die Ausnahme weiterhin unbeobachtet bleibt, wird die Exception-Eskalationsrichtlinie aktiviert, indem die Exception im Finalizer-Thread nicht behandelt wird.

    
Yuval Itzchakov 20.05.2014 18:11
quelle
4

Aus dem Kommentar:

  

Diese [Aufgaben] waren an verwaltete Ressourcen gebunden, und ich wollte sie dann freigeben   Sie wurden verfügbar, anstatt darauf zu warten, dass sie alle fertig waren   und dann loslassen.

Wenn Sie die Methode helper async void verwenden, erhalten Sie möglicherweise das gewünschte Verhalten, indem Sie die fertigen Aufgaben aus der Liste entfernen und sofort unbeobachtete Ausnahmen auslösen:

%Vor%

Dann könnte Ihr Code so aussehen (ungetestet):

%Vor%

.Observe() wird die Ausnahme der Aufgabe sofort "out-of-band" zurückwerfen, wobei SynchronizationContext.Post verwendet wird, wenn der aufrufende Thread einen Synchronisationskontext hat, oder andernfalls ThreadPool.QueueUserWorkItem . Sie können solche "Out-of-Band" -Ausnahmen mit AppDomain.CurrentDomain.UnhandledException behandeln.

Ich habe das hier genauer beschrieben:

TAP globale Ausnahmebehandlung

    
Noseratio 21.05.2014 00:01
quelle

Tags und Links