.net c # Die beste Methode, auf asynchrone Ereignisse zu warten, um einen synchronen Ablauf in Ihrem Code zu erreichen

8

Meine generalisierte Frage lautet: Wie schreibt man asynchronen Code, der immer noch klar und einfach zu verstehen ist, wie eine synchrone Lösung?

Ich habe die Erfahrung gemacht, dass wenn Sie etwas synchronen Code asynchron machen müssen, indem Sie etwas wie BackgroundWorker verwenden, haben Sie nicht mehr eine Reihe von einfach zu befolgenden Programmanweisungen, die Ihre gesamte Absicht und Reihenfolge von Aktivitäten ausdrücken Eine Reihe von "Done" -Ereignishandlern, von denen jeder den nächsten BackgroundWorker startet, der Code produziert, der wirklich schwer zu befolgen ist.

Ich weiß, das ist nicht sehr klar; etwas konkreteres:

Nehmen wir an, eine Funktion in meiner WinForms-Anwendung muss einige Amazon EC2-Instanzen starten, darauf warten, dass sie ausgeführt werden, und dann darauf warten, dass alle eine SSH-Verbindung akzeptieren. Eine synchrone Lösung in Pseudo-Code könnte so aussehen:

%Vor%

Das ist nett. Was passiert, ist sehr klar und die Reihenfolge der Programmaktionen ist sehr klar. Kein weißes Rauschen, das Sie vom Verständnis des Codes und des Flusses ablenkt. Ich würde wirklich gerne mit Code, der so aussieht, enden.

Aber in Wirklichkeit kann ich keine synchrone Lösung haben .. jede dieser Funktionen kann für eine lange Zeit laufen, und jeder muss Dinge tun: update die ui, überwache Zeitüberschreitungen und versuche es erneut Operationen periodisch bis zum Erfolg oder Timeout. Kurz gesagt, jeder dieser Vorgänge muss im Hintergrund ausgeführt werden, damit der Vordergrund-UI-Thread fortgesetzt werden kann.

Aber wenn ich Lösungen wie BackgroundWorker verwende, scheint es, als ob ich nicht mit einer einfach zu befolgenden Programmlogik wie der oben genannten enden würde. Stattdessen starte ich möglicherweise einen Hintergrund-Worker von meinem UI-Thread, um die erste Funktion auszuführen, und dann kehrt mein UI-Thread zur UI zurück, während der Arbeitsthread ausgeführt wird. Wenn es fertig ist, startet der Event-Handler "done" möglicherweise den nächsten Background Worker. Wenn es fertig ist, startet der "done" -Ereignishandler möglicherweise den letzten BackgroundWorker und so weiter. Das bedeutet, dass Sie den Spuren der Ereignisbehandlungsroutinen "Erledigt" folgen müssen, um den gesamten Programmablauf zu verstehen.

Es muss einen besseren Weg geben, dass a) meinen Benutzeroberflächen-Thread anspricht, b) meine asynchronen Operationen in der Lage sein müssen, das ui zu aktualisieren und vor allem c) mein Programm als Folge von aufeinanderfolgenden Schritten auszudrücken (z Ich habe oben gezeigt), damit jemand den resultierenden Code verstehen kann

Alle Eingaben würden sehr geschätzt! Michael

    
Michael Ray Lovett 26.07.2012, 17:11
quelle

5 Antworten

11
  

Meine generalisierte Frage lautet: Wie schreibt man asynchronen Code, der immer noch klar und einfach zu verstehen ist, wie eine synchrone Lösung?

Sie warten auf C # 5. Es wird nicht mehr lange dauern. async / await rockt. Sie haben das Feature im obigen Satz wirklich beschrieben ... Weitere Informationen finden Sie auf der Visual Studio-asynchronen Startseite für Tutorials, die Sprachspezifikation, Downloads etc.

Im Moment ist wirklich

Asynchrone Code wird sehr natürlich ein Chaos, vor allem wenn Sie Fehlerbehandlung etc.

betrachten

Ihr Code würde folgendermaßen ausgedrückt:

%Vor%

Das setzt ein kleines bisschen Extraarbeit voraus, zB eine Erweiterungsmethode für IEnumerable<T> mit dem Formular

%Vor%     
Jon Skeet 26.07.2012, 17:15
quelle
3

Wenn Sie nicht auf fünf warten können, wie Jon zu Recht vorschlägt, würde ich vorschlagen, dass Sie sich die Task Parallel Library (Bestandteil von .NET 4). Es bietet eine Menge der Rohrleitungen rund um das "Tun Sie dies asynchron, und wenn es fertig ist, dass" Paradigma zu tun, die Sie in der Frage beschreiben. Es hat auch solide Unterstützung für die Fehlerbehandlung in den asynchronen Aufgaben selbst.

    
Chris Shain 26.07.2012 17:20
quelle
3

Async/await ist wirklich der beste Weg zu gehen. Wenn Sie jedoch nicht warten möchten, können Sie Continuation-passing-style oder CPS ausprobieren. Dazu übergeben Sie einen Delegaten an die asynchrone Methode, die aufgerufen wird, wenn die Verarbeitung abgeschlossen ist. Meiner Meinung nach ist dies sauberer als alle zusätzlichen Ereignisse.

Das wird diese Methodensignatur ändern

%Vor%

An

%Vor%

Dann, um es zu benutzen, hättest du

%Vor%

Dies wird ein wenig schwierig, wenn GetFoo eine Ausnahme auslösen könnte. Die Methode, die ich bevorzuge, ist, die Signatur von GetFooAsync zu ändern.

%Vor%

Ihre Rückrufmethode sieht wie folgt aus

%Vor%

Bei anderen Methoden müssen dem Callback zwei Parameter übergeben werden: das tatsächliche Ergebnis und eine Ausnahme.

%Vor%

Dies beruht darauf, dass der Callback nach einer Ausnahme sucht, die es erlauben könnte, sie zu ignorieren. Andere Methoden haben zwei Rückrufe, einen für Erfolg und einen für Fehler.

%Vor%

Dies macht den Ablauf für mich komplizierter und erlaubt es immer noch, die Ausnahme zu ignorieren.

Wenn Sie dem Callback jedoch eine Methode geben, die aufgerufen werden muss, um das Ergebnis zu erhalten, wird der Callback gezwungen, die Exception zu behandeln.

    
cadrell0 26.07.2012 17:35
quelle
1
  

Wenn der Vorgang beendet ist, startet der Ereignishandler "done" möglicherweise den nächsten Hintergrund-Worker.

Das ist etwas, mit dem ich seit einiger Zeit zu kämpfen habe. Im Grunde warten Sie darauf, dass ein Prozess beendet wird, ohne die Benutzeroberfläche zu sperren.

Anstatt einen backgroundWorker zu verwenden, um einen backgroundWorker zu starten, können Sie jedoch einfach alle Aufgaben in einem backgroundWorker erledigen. In der Funktion backgroundWorker.DoWork wird es synchron für diesen Thread ausgeführt. So können Sie eine DoWork-Funktion haben, die alle 3 Elemente verarbeitet.

Dann musst du nur auf den einen BackgroundWorker.Completed warten und "cleaner" Code haben.

Sie können also mit

enden %Vor%     
Trevor Watson 26.07.2012 17:25
quelle
0

In einigen Szenarien (wird später erläutert) können Sie die asynchronen Aufrufe an eine Methode wie den folgenden Pseudocode umschließen:

%Vor%

Damit der obige Code funktioniert, muss der Rückruf von einem anderen Thread aufgerufen werden. Das hängt davon ab, mit was Sie arbeiten. Aus meiner Erfahrung werden zumindest Silverlight-Web-Service-Aufrufe im UI-Thread behandelt, dh das obige Muster kann nicht verwendet werden. Wenn der UI-Thread blockiert ist, kann der vorherige Aufruf nicht ausgeführt werden. Wenn Sie mit dieser Art von Frameworks arbeiten, besteht eine weitere Möglichkeit, mehrere asynchrone Aufrufe durchzuführen, darin, Ihre Logik auf höherer Ebene in einen Hintergrundthread zu verschieben und den UI-Thread für die Kommunikation zu verwenden. Dieser Ansatz ist jedoch in den meisten Fällen ein wenig über das Töten, da es einen Vorprogramm-Code zum Starten und Stoppen des Hintergrund-Threads benötigt.

    
Codism 26.07.2012 18:12
quelle

Tags und Links