Implementieren des ereignisbasierten asynchronen Entwurfsmusters
Wenn Sie eine Klasse mit Operationen schreiben, durch die nennenswerte Verzögerungen auftreten können, sollten Sie sie mit einer asynchronen Funktionalität ausstatten, indem Sie Übersicht über ereignisbasierte asynchrone Muster implementieren.
Für das Verpacken einer Klasse, die asynchrone Features besitzt, hält das ereignisbasierte asynchrone Muster eine standardisierte Vorgehensweise bereit. Sofern Sie Ihre Klasse mit Hilfsklassen wie AsyncOperationManager implementieren, ist die korrekte Funktionsweise dieser Klasse unter jedem beliebigen Anwendungsmodell gewährleistet, einschließlich ASP.NET, Konsolenanwendungen und Windows Forms-Anwendungen.
Ein Beispiel, in dem das ereignisbasierte asynchrone Muster implementiert wird, finden Sie unter Gewusst wie: Implementieren einer Komponente, die das ereignisbasierte asynchrone Muster unterstützt.
Für einfache asynchrone Operationen ist häufig die BackgroundWorker-Komponente geeignet. Weitere Informationen zur BackgroundWorker-Komponente finden Sie unter Gewusst wie: Ausführen eines Vorgangs im Hintergrund.
In der folgenden Liste finden Sie eine Beschreibung der in diesem Thema behandelten Features des ereignisbasierten asynchronen Entwurfsmusters.
Geeignete Situationen für die Implementierung des ereignisbasierten asynchronen Musters
Benennen von asynchronen Methoden
Optionale Unterstützung von Abbrüchen
Optionale Unterstützung der IsBusy-Eigenschaft
Optionale Unterstützung der Statusmeldung
Optionale Unterstützung der Rückgabe inkrementeller Ergebnisse
Handhaben des Out-Parameters und des Ref-Parameters in Methoden
Geeignete Situationen für die Implementierung des ereignisbasierten asynchronen Musters
Ziehen Sie die Implementierung des ereignisbasierten asynchronen Musters für folgende Fälle in Betracht:
Die Clients der Klasse benötigen das WaitHandle-Objekt und das IAsyncResult-Objekt, die für asynchrone Operationen zur Verfügung stehen, nicht. Dies bedeutet, dass der Abruf sowie WaitAll oder WaitAny vom Client erstellt werden müssen.
Sie möchten, dass asynchrone Operationen vom Client mithilfe des vertrauten Ereignis- und Delegatmodells verwaltet werden.
Eine asynchrone Implementierung ist zwar für jede Operation möglich, jedoch sollten Sie solche Operationen in Erwägung ziehen, bei denen lange Wartezeiten zu befürchten sind. Besonders geeignet sind Operationen, bei denen Clients eine Methode aufrufen und über den Abschluss eine Benachrichtigung erhalten, ohne dass weitere Bedieneingriffe erforderlich sind. Geeignet sind darüber hinaus auch Operationen, die fortlaufend ausgeführt werden und in bestimmten Zeitabständen die Clients in Bezug auf Status, inkrementelle Ergebnisse oder Zustandsänderungen benachrichtigen.
Weitere Informationen zu den Einsatzmöglichkeiten des ereignisbasierten asynchronen Musters erhalten Sie unter Gründe für das Implementieren des ereignisbasierten asynchronen Musters.
Benennen von asynchronen Methoden
Wenn Sie für eine einzelne synchrone Methode MethodName ein asynchrones Pendant bereitstellen möchten, gehen Sie folgendermaßen vor:
Definieren Sie eine MethodNameAsync-Methode mit den folgenden Eigenschaften:
Gibt void zurück.
Weist die gleichen Parameter auf wie die MethodName-Methode.
Akzeptiert mehrere Aufrufe.
Definieren Sie optional eine MethodNameAsync-Überladung, die mit MethodNameAsync übereinstimmt, aber mit einem zusätzlichen Objektwertparameter mit dem Namen userState ausgestattet ist. Sie verfolgen diesen Ansatz, wenn Sie für mehrere gleichzeitige Aufrufe der Methode bereits die notwendigen Vorbereitungen getroffen haben. In einem solchen Fall wird der userState-Wert an alle Ereignishandler zurückgegeben, um die einzelnen Aufrufe der Methode voneinander unterscheiden zu können. Sie können diese Vorgehensweise auch einfach zum Speichern des Benutzerzustands wählen, damit dieser später abgerufen werden kann.
Definieren Sie für jede separate MethodNameAsync-Methodensignatur Folgendes:
Definieren Sie das folgende Ereignis in der gleichen Klasse wie die Methode:
Public Event MethodNameCompleted As MethodNameCompletedEventHandler
public event MethodNameCompletedEventHandler MethodNameCompleted;
Definieren Sie den folgenden Delegaten und das folgende AsyncCompletedEventArgs. Diese werden wahrscheinlich außerhalb der eigentlichen Klasse definiert, aber dennoch im gleichen Namespace.
Public Delegate Sub MethodNameCompletedEventHandler( _ ByVal sender As Object, _ ByVal e As MethodNameCompletedEventArgs) Public Class MethodNameCompletedEventArgs Inherits System.ComponentModel.AsyncCompletedEventArgs Public ReadOnly Property Result() As MyReturnType End Property
public delegate void MethodNameCompletedEventHandler(object sender, MethodNameCompletedEventArgs e); public class MethodNameCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { public MyReturnType Result { get; } }
Vergewissern Sie sich, dass die MethodNameCompletedEventArgs-Klasse ihre Member als schreibgeschützte Eigenschaften und nicht als Felder verfügbar macht, da Felder die Datenbindung verhindern.
Definieren Sie für Methoden, die keine Ergebnisse produzieren, keine von AsyncCompletedEventArgs abgeleiteten Klassen. Verwenden Sie einfach eine Instanz, die AsyncCompletedEventArgs eigen ist.
Hinweis Die Wiederverwendung von Delegat und AsyncCompletedEventArgs-Typen ist, sofern dies machbar ist und angemessen erscheint, durchaus akzeptabel.In einem solchen Fall fällt die Benennung in Bezug auf den Methodennamen nicht ganz so konsistent aus, da ein gegebener Delegat sowie das AsyncCompletedEventArgs nicht an eine einzelne Methode gebunden sind.
Optionale Unterstützung von Abbrüchen
Wenn Ihre Klasse den Abbruch asynchroner Operationen unterstützen soll, dann ist dieser Abbruch dem Client wie im Folgenden beschrieben verfügbar zu machen. Beachten Sie, dass es zwei Entscheidungspunkte gibt, die vor dem Definieren der Abbruchunterstützung erreicht werden müssen:
Besitzt Ihre Klasse, einschließlich zukünftiger Erweiterungen, nur eine asynchrone Operation, durch die ein Abbruch unterstützt wird?
Bieten die asynchronen Operationen, die den Abbruch unterstützen, auch die Unterstützung von mehreren ausstehenden Operationen? Weist die MethodNameAsync-Methode einen userState-Parameter auf, und sind bei ihr mehrere Aufrufe möglich, ohne das auf den Abschluss jedes einzelnen Aufrufes gewartet werden muss?
Verwenden Sie die Antworten auf diese beiden Fragen in der untenstehenden Tabelle, um die Signatur Ihrer Abbruchmethode zu bestimmen.
Visual Basic
|
Gleichzeitige Unterstützung mehrerer Operationen |
Unterstützung von jeweils nur einer Operation |
---|---|---|
Eine asynchrone Operation in der gesamten Klasse |
|
|
Mehrere asynchrone Operationen in der Klasse |
|
|
C#
|
Gleichzeitige Unterstützung mehrerer Operationen |
Unterstützung von jeweils nur einer Operation |
---|---|---|
Eine asynchrone Operation in der gesamten Klasse |
|
|
Mehrere asynchrone Operationen in der Klasse |
|
|
Falls Sie die CancelAsync(object userState) -Methode definieren, müssen die Clients bei der Wahl ihrer Zustandswerte darauf achten, dass diese Werte auch zwischen allen im Objekt aufgerufenen asynchronen Methoden und nicht nur zwischen allen Aufrufen einer einzelnen asynchronen Methode differenzieren.
Die Entscheidung, der einzelnen asynchronen Operationsversion den Namen MethodNameAsyncCancel zu geben, basiert auf dem Gedanken, in einer Entwurfsumgebung wie IntelliSense von Visual Studio den Vorgang der Methodenerkennung zu vereinfachen. Die zugehörigen Member werden in einer Gruppe vereint und von anderen Membern unterschieden, die mit der asynchronen Funktionalität nicht in Verbindung stehen. Wenn davon ausgegangen werden kann, dass in nachfolgenden Versionen weitere asynchrone Operationen hinzugefügt werden, ist es besser, CancelAsync zu definieren.
Definieren Sie von den oben in der Tabelle aufgeführten Methoden nicht mehrere in derselben Klasse. Diese Methodenanhäufung ist nicht sinnvoll bzw. erweist sich in Bezug auf die Klassenschnittstelle als ungünstig.
Diese Methoden geben i. d. R. sofort einen Wert zurück, und die Operation kann u. U. sogar einen Abbruch vornehmen. Im Ereignishandler für das MethodNameCompleted-Ereignis enthält das MethodNameCompletedEventArgs-Objekt ein Cancelled-Feld, das von den Clients verwendet werden kann um festzustellen, ob ein Abbruch aufgetreten ist.
Halten Sie sich an die in Bewährte Verfahrensweisen für das Implementieren des ereignisbasierten asynchronen Entwurfsmusters beschriebene Semantik, die Abbrüchen zugrunde liegt.
Optionale Unterstützung der IsBusy-Eigenschaft
Sofern von der Klasse nicht mehrere Aufrufe gleichzeitig unterstützt werden, ist es empfehlenswert, eine IsBusy-Eigenschaft zur Verfügung zu stellen. Über diese Eigenschaft kann dann bestimmt werden, ob die MethodNameAsync-Methode ausgeführt werden kann, ohne dass von der MethodNameAsync-Methode eine Ausnahme abgefangen werden muss.
Halten Sie sich an die in Bewährte Verfahrensweisen für das Implementieren des ereignisbasierten asynchronen Entwurfsmusters beschriebene IsBusy-Semantik.
Optionale Unterstützung der Statusmeldung
Es ist häufig wünschenswert, dass eine asynchrone Operation bei Ausführung auch den Status meldet. Hierfür enthält das ereignisbasierte asynchrone Muster eine Richtlinie.
Definieren Sie optional ein Ereignis, das von der asynchronen Operation ausgelöst und im entsprechenden Thread aufgerufen werden soll. Das ProgressChangedEventArgs-Objekt enthält eine Statusanzeige mit ganzen Zahlen von 0 (null) bis 100 (einhundert).
Nennen Sie dieses Ereignis wie folgt:
ProgressChanged, sofern die Klasse mehrere asynchrone Operationen besitzt (oder vermutlich in zukünftigen Versionen noch um mehrere asynchrone Operationen erweitert wird);
MethodNameProgressChanged, wenn die Klasse über eine einzelne asynchrone Operation verfügt.
Diese Namensgebung ist an die der Abbruchmethode angelehnt. Eine Beschreibung finden Sie im Abschnitt zur optionalen Unterstützung von Abbrüchen.
Dieses Ereignis sollte die Signatur des ProgressChangedEventHandler-Delegaten und die ProgressChangedEventArgs-Klasse verwenden. Alternativ sollten Sie eine von ProgressChangedEventArgs abgeleitete Klasse definieren, wenn eine für die Anwendungsdomäne spezifischere Statusanzeige (beispielsweise eine Anzeige, die für eine Download-Operation die Bytes liest und die Gesamtzahl der Bytes angibt) bereitgestellt werden kann.
Beachten Sie, dass es für die Klasse nur ein ProgressChanged-Ereignis oder ein MethodNameProgressChanged-Ereignis gibt, unabhängig von der Zahl der unterstützten asynchronen Methoden. Es wird erwartet, dass Clients das userState-Objekt verwenden, das an die MethodNameAsync-Methoden übergeben wird, um zwischen Fortschrittsaktualisierungen bei mehreren, gleichzeitigen Operationen zu differenzieren.
Wenn mehrere Operationen die Fortschrittsberichterstellung unterstützen, ist es nicht ausgeschlossen, das jede Operation jeweils eine andere Statusanzeige zurückgibt. In diesem Fall ist ein einzelnes ProgressChanged-Ereignis nicht angemessen, sodass Sie das Unterstützen mehrerer ProgressChanged-Ereignisse in Erwägung ziehen sollten. Verwenden Sie in diesem Fall für jede MethodNameAsync-Methode ein Benennungsschema nach der Art MethodNameProgressChanged.
Halten Sie sich an die unter Bewährte Verfahrensweisen für das Implementieren des ereignisbasierten asynchronen Entwurfsmusters beschriebene Semantik für die Fortschrittsberichterstellung.
Optionale Unterstützung der Rückgabe inkrementeller Ergebnisse
Mitunter werden inkrementelle Ergebnisse von einer asynchronen Operation vor deren Abschluss zurückgegeben. Zur Unterstützung dieses Szenarios können Sie von mehreren Möglichkeiten Gebrauch machen. Im Folgenden werden einige Beispiele genannt.
Klasse mit einer einzelnen Operation
Wenn von Ihrer Klasse nur eine einzelne asynchrone Operation unterstützt wird, und diese inkrementelle Ergebnisse zurückgeben kann, dann gehen Sie folgendermaßen vor:
Erweitern Sie den ProgressChangedEventArgs-Typ, damit dieser die inkrementellen Ergebnisdaten aufnehmen kann, und legen Sie mit diesen erweiterten Daten ein MethodNameProgressChanged-Ereignis fest.
Lösen Sie das MethodNameProgressChanged-Ereignis aus, wenn es ein inkrementelles Ergebnis zu berichten gibt.
Diese Lösung bietet sich insbesondere für eine Klasse mit einer einzelnen asynchronen Operation an, da durch dieses Ereignis nicht das Problem auftreten kann, dass inkrementelle Ergebnisse für "alle Operationen" zurückgegeben werden, wie es beim MethodNameProgressChanged-Ereignis der Fall ist.
Klasse mit mehreren Operationen und homogenen, inkrementellen Ergebnissen
Von einer solchen Klasse werden mehrere, asynchrone Methoden unterstützt, die alle inkrementellen Ergebnisse mit ein und demselben Datentyp zurückgeben können.
Folgen Sie bei Klassen mit einer einzelnen Operation dem oben beschriebenen Modell, da diese EventArgs-Struktur für alle inkrementellen Ergebnisse geeignet ist. Definieren Sie ein ProgressChanged-Ereignis statt eines MethodNameProgressChanged-Ereignisses, da es für mehrere asynchrone Methoden gilt.
Klasse mit mehreren Operationen und heterogenen, inkrementellen Ergebnissen
Wenn von der Klasse mehrere asynchrone Methoden unterstützt werden, und jede dieser Methoden einen anderen Datentyp zurückgibt, empfiehlt sich Folgendes:
Führen Sie die Berichterstellung zu inkrementellen Ergebnissen und die Fortschrittsberichterstellung separat aus.
Legen Sie für die Verarbeitung der inkrementellen Ergebnisdaten jeder asynchronen Methode ein separates MethodNameProgressChanged-Ereignis mit den entsprechenden EventArgs fest.
Rufen Sie diesen Ereignishandler für den entsprechenden Thread wie unter Bewährte Verfahrensweisen für das Implementieren des ereignisbasierten asynchronen Entwurfsmusters beschrieben auf.
Handhaben des Out-Parameters und des Ref-Parameters in Methoden
Zwar wird in .Net Framework im Allgemeinen von der Verwendung von out und ref abgeraten, sollten diese aber dennoch vorhanden sein, ist nach folgenden Regeln zu verfahren:
Bei einer synchronen MethodName-Methode gilt Folgendes:
Der out-Parameter von MethodName sollte nicht Teil von MethodNameAsync sein. Stattdessen sollte er zu MethodNameCompletedEventArgs gehören und den gleichen Namen wie dessen Parameter in MethodName haben (wenn kein treffenderer Name zur Verfügung steht).
Der ref-Parameter von MethodName sollte als Teil von MethodNameAsync und MethodNameCompletedEventArgs erscheinen und den gleichen Namen wie deren Parameter in MethodName haben (wenn kein treffenderer Name zur Verfügung steht).
Der Code könnte wie folgt lauten:
Public Function MethodName(ByVal arg1 As String, ByRef arg2 As String, ByRef arg3 As String) As Integer
public int MethodName(string arg1, ref string arg2, out string arg3);
Die asynchrone Methode und ihre AsyncCompletedEventArgs-Klasse sähen dann folgendermaßen aus:
Public Sub MethodNameAsync(ByVal arg1 As String, ByVal arg2 As String)
Public Class MethodNameCompletedEventArgs
Inherits System.ComponentModel.AsyncCompletedEventArgs
Public ReadOnly Property Result() As Integer
End Property
Public ReadOnly Property Arg2() As String
End Property
Public ReadOnly Property Arg3() As String
End Property
End Class
public void MethodNameAsync(string arg1, string arg2);
public class MethodNameCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs
{
public int Result { get; };
public string Arg2 { get; };
public string Arg3 { get; };
}
Siehe auch
Aufgaben
Gewusst wie: Implementieren einer Komponente, die das ereignisbasierte asynchrone Muster unterstützt
Gewusst wie: Ausführen eines Vorgangs im Hintergrund
Gewusst wie: Implementieren eines Formulars, das eine Hintergrundoperation verwendet
Referenz
Konzepte
Gründe für das Implementieren des ereignisbasierten asynchronen Musters
Bewährte Verfahrensweisen für das Implementieren des ereignisbasierten asynchronen Entwurfsmusters
Weitere Ressourcen
Multithreadprogrammierung mit dem ereignisbasierten asynchronen Muster