Wie kann man bei der Verwendung von async / await besser mit entsorgten Steuerelementen umgehen?

10

Betrachten Sie diesen Code, der auf dem UI-Thread ausgeführt wird:

%Vor%

Beachten Sie, dass ich bei jedem await auch IsDisposed überprüfe. Es ist notwendig, weil ich await für eine lange laufende Task sage. Der Benutzer schließt das Formular vor dem Abschluss. Das Task beendet und führt eine Fortsetzung aus, die versucht, auf Steuerelemente in einem entsorgten Formular zuzugreifen. Eine Ausnahme tritt auf.

Gibt es einen besseren Weg, dies zu handhaben oder dieses Muster zu vereinfachen? Ich verwende await großzügig im UI-Code und es ist sowohl hässlich, jedes Mal nach IsDisposed zu suchen als auch fehleranfällig, wenn ich es vergesse.

BEARBEITEN:

Es gibt einige vorgeschlagene Lösungen, die nicht zur Rechnung passen, weil sie die Funktionalität ändern.

  • Verhindern, dass das Formular geschlossen wird, bis Hintergrundaufgaben abgeschlossen sind

Dies wird die Benutzer frustrieren. Und es ermöglicht auch weiterhin potentiell teure GUI-Arbeit, die Zeitverschwendung ist, die Leistung beeinträchtigt und nicht länger relevant ist. In dem Fall, in dem ich fast immer Hintergrundarbeit mache, könnte dies das Formular für sehr lange Zeit schließen.

  • Verstecke das Formular und schließe es, sobald alle Aufgaben abgeschlossen sind.

Dies hat alle Probleme, das Schließen des Formulars zu verhindern, außer die Benutzer nicht zu frustrieren. Die Fortsetzungen, die teure GUI-Arbeit verrichten, werden weiterhin ausgeführt. Es fügt auch die Komplexität der Verfolgung hinzu, wenn alle Aufgaben abgeschlossen sind, und schließt das Formular, wenn es ausgeblendet ist.

  • Verwenden Sie CancellationTokenSource , um alle Aufgaben beim Schließen des Formulars abzubrechen

Dies geht nicht einmal auf das Problem ein. Tatsächlich mache ich das schon (kein Grund, Hintergrundressourcen zu verschwenden). Dies ist keine Lösung, da ich IsDisposed aufgrund einer impliziten Racebedingung noch überprüfen muss. Der folgende Code zeigt die Wettlaufsituation.

%Vor%

Die Race Condition passiert, wenn die Aufgabe bereits abgeschlossen ist, das Formular geschlossen wurde und die Fortsetzung noch läuft. Sie werden feststellen, dass InvalidOperationException gelegentlich ausgelöst wird. Das Abbrechen der Aufgabe ist eine gute Übung, aber es hindert mich nicht daran, IsDisposed zu überprüfen.

KLARIFIZIERUNG

Das ursprüngliche Codebeispiel ist genau was ich in Bezug auf die Funktionalität möchte. Es ist nur ein hässliches Muster, und es ist ein recht verbreiteter Anwendungsfall, "Hintergrundarbeit zu erwarten, dann GUI zu aktualisieren". Technisch möchte ich nur, dass die Fortsetzung überhaupt nicht läuft, wenn das Formular entsorgt wird. Der Beispielcode tut genau das, aber nicht elegant und ist fehleranfällig (wenn ich vergess, IsDisposed auf jedem einzelnen await zu überprüfen, führe ich einen Bug ein). Idealerweise möchte ich einen Wrapper, eine Erweiterungsmethode usw. schreiben, die diesen grundlegenden Entwurf einkapseln könnten. Aber ich kann mir keinen Weg vorstellen, dies zu tun.

Ich muss auch sagen, dass die Leistung eine erstklassige Überlegung ist. Eine Ausnahme zu werfen ist beispielsweise aus Gründen, auf die ich nicht eingehen werde, sehr teuer. Also ich möchte nicht nur versuchen, ObjectDisposedException einzufangen, wenn ich ein await mache. Noch hässlicher Code und verletzt auch Leistung. Es scheint, als wäre es nur eine IsDisposed Prüfung jedes Mal, wenn es die beste Lösung ist, aber ich wünschte, es gäbe einen besseren Weg.

BEARBEITEN # 2

Hinsichtlich der Leistung - ja, es ist alles relativ. Ich verstehe, dass die große Mehrheit der Entwickler sich nicht um die Kosten für das Auslösen von Ausnahmen kümmert. Die wahren Kosten für das Auslösen einer Ausnahme sind off-subject. Hierüber gibt es viele Informationen. Es genügt zu sagen, dass es um viele Größenordnungen teurer ist als der if (IsDisposed) check. Für mich sind die Kosten unnötiger Ausnahmeregelungen inakzeptabel. Ich sage in diesem Fall unnötig, weil ich bereits eine Lösung habe, die keine Ausnahmen auslöst. Auch hier ist es eine nicht akzeptable Lösung, eine Fortsetzung werfen zu lassen und genau das, was ich zu vermeiden versuche.

    
Zer0 25.06.2015, 23:08
quelle

3 Antworten

2

Ich verwende auch IsDisposed , um den Zustand des Steuerelements in solchen Situationen zu überprüfen. Obwohl es ein bisschen ausführlich ist, ist es nicht ausführlicher als notwendig, um mit der Situation umzugehen - und es ist überhaupt nicht verwirrend. Eine funktionale Sprache wie F # mit Monaden könnte hier vielleicht helfen - ich bin kein Experte - aber das scheint so gut wie in C # zu sein.

    
bright 26.06.2015, 19:15
quelle
2

Es sollte ziemlich einfach sein, ein CancellationTokenSource im Besitz Ihres Formulars zu haben und das Formular Cancel aufzurufen, wenn es geschlossen ist.

Dann können Ihre async -Methoden CancellationToken beobachten.

    
Stephen Cleary 26.06.2015 01:29
quelle
0

Ich habe ein ähnliches Problem gelöst, indem ich das Formular nicht geschlossen habe. Stattdessen versteckte ich es zuerst und schloss es erst wirklich, wenn alle hervorragenden Arbeiten abgeschlossen waren. Ich musste diese Arbeit natürlich in Form von Task Variablen verfolgen.

Ich halte dies für eine saubere Lösung, da keine Entsorgungsprobleme auftreten. Der Benutzer kann das Formular jedoch sofort schließen.

    
usr 26.06.2015 19:17
quelle