Bewährte Verfahrensweisen für das Implementieren des ereignisbasierten asynchronen Entwurfsmusters
Mit dem ereignisbasierten asynchronen Entwurfsmuster können Sie auf effiziente Art und Weise asynchrones Verhalten in Klassen bereitstellen, und zwar mit der vertrauten Semantik für Ereignisse und Delegaten. Beim Implementieren des ereignisbasierten asynchronen Musters müssen Sie sich an einige bestimmte Vorgehensweisen halten. In den folgenden Abschnitten werden diese Verhaltensweisen und Richtlinien beschrieben, die Sie beim Implementieren einer Klasse, die dem ereignisbasierten asynchronen Muster folgt, einhalten müssen.
Eine Übersicht finden Sie unter Implementieren des ereignisbasierten asynchronen Entwurfsmusters.
In der folgenden Liste werden die empfohlenen Vorgehensweisen angezeigt, die in diesem Thema behandelt werden:
Erforderliche Verhaltensgarantien
Abschluss
Abgeschlossenes Ereignis und abgeschlossene EventArgs
Gleichzeitiges Ausführen von Operationen
Zugreifen auf Ergebnisse
Fortschrittsberichterstellung
IsBusy-Implementierung
Abbruch
Fehler und Ausnahmen
Threading und Kontexte
Richtlinien
Erforderliche Verhaltensgarantien
Falls Sie das ereignisbasierte asynchrone Muster implementieren, sind einige Garantien erforderlich, damit Sie gewährleisten können, dass sich die Klasse richtig verhält und dass sich Clients dieser Klasse auf dieses Verhalten verlassen können.
Abschluss
Bei erfolgreichem Abschluss, einem Fehler oder einem Abbruch rufen Sie immer den MethodNameCompleted-Ereignishandler auf. Auf Anwendungen sollte nie die Tatsache zutreffen, dass Sie sich im Leerlauf befinden und nie abgeschlossen werden. Eine Ausnahme von der Regel stellt der Sachverhalt dar, dass die asynchrone Operation selbst so entworfen ist, dass sie nie abschließt.
Abgeschlossenes Ereignis und abgeschlossene EventArgs
Wenden Sie auf jede separate MethodNameAsync-Methode die folgenden Entwurfsanforderungen an:
Definieren Sie ein MethodNameCompleted-Ereignis in der gleichen Klasse wie die Methode.
Definieren Sie eine EventArgs-Klasse sowie den zugehörigen Delegaten für das MethodNameCompleted-Ereignis, das von der AsyncCompletedEventArgs-Klasse abgeleitet ist. Der Standardklassenname sollte das Format MethodNameCompletedEventArgs haben.
Stellen Sie sicher, dass die EventArgs-Klasse spezifisch zu den Rückgabewerten der MethodName-Methode ist. Wenn Sie die EventArgs-Klasse verwenden, sollten Sie an Entwickler nie die Anforderung stellen, das Ergebnis umzuwandeln.
Im folgenden Codebeispiel werden sowohl eine gültige als auch eine ungültige Implementierung dieser Entwurfsanforderung gezeigt.
[C#]
// Good design
private void Form1_MethodNameCompleted(object sender, xxxCompletedEventArgs e)
{
DemoType result = e.Result;
}
// Bad design
private void Form1_MethodNameCompleted(object sender, MethodNameCompletedEventArgs e)
{
DemoType result = (DemoType)(e.Result);
}
Definieren Sie keine EventArgs-Klasse für die Rückgabe von Methoden, die void zurückgeben. Verwenden Sie stattdessen eine Instanz der AsyncCompletedEventArgs-Klasse.
Stellen Sie sicher, dass Sie immer das MethodNameCompleted-Ereignis auslösen. Dieses Ereignis muss beim erfolgreichen Abschluss, bei einem Fehler und bei einem Abbruch ausgelöst werden. Auf Anwendungen sollte nie die Tatsache zutreffen, dass Sie sich im Leerlauf befinden und nie abgeschlossen werden.
Stellen Sie sicher, dass Sie alle Ausnahmen abfangen, die im asynchronen Vorgang auftreten, und weisen Sie die abgefangene Ausnahme der Error-Eigenschaft zu.
Wenn beim Abschließen der Aufgabe ein Fehler aufgetreten ist, darf auf die Ergebnisse nicht zugegriffen werden können. Wenn die Error-Eigenschaft nicht null ist, stellen Sie sicher, dass jeder Zugriff auf die Eigenschaften in der EventArgs-Struktur eine Ausnahme auslöst. Verwenden Sie hierzu die RaiseExceptionIfNecessary-Methode.
Modellieren Sie ein Timeout als Fehler. Wenn ein Timeout auftritt, lösen Sie das MethodNameCompleted-Ereignis aus, und weisen Sie der Error-Eigenschaft eine TimeoutException zu.
Wenn die Klasse mehrere gleichzeitige Aufrufe unterstützt, stellen Sie sicher, dass das MethodNameCompleted-Ereignis das richtige userSuppliedState-Objekt enthält.
Stellen Sie sicher, dass das MethodNameCompleted-Ereignis im richtigen Thread und zur richtigen Zeit im Anwendungslebenszyklus ausgelöst wird. Weitere Informationen finden Sie im Abschnitt über Threading und Kontexte.
Gleichzeitiges Ausführen von Operationen
Wenn von Ihrer Klasse mehrere gleichzeitige Aufrufe unterstützt werden, sollte der Entwickler jeden Aufruf separat verfolgen können. Dazu definieren Sie die MethodNameAsync-Überladung, die einen Zustandsparameter mit Objektwert bzw. eine Aufgaben-ID mit dem Namen userSuppliedState übernimmt. Dieser Parameter sollte immer der letzte Parameter in der Signatur der MethodNameAsync-Methode sein.
Wenn Ihre Klasse die MethodNameAsync-Überladung definiert, die einen Zustandsparameter mit Objektwert bzw. eine Aufgaben-ID übernimmt, vergewissern Sie sich, dass Sie mit dieser Aufgaben-ID die Lebensdauer der Operation verfolgen und diese Aufgaben-ID dann auch wieder dem Abschlusshandler zur Verfügung stellen. Als Hilfe stehen Hilfsklassen zur Verfügung. Weitere Informationen zur Parallelitätsverwaltung finden Sie unter Exemplarische Vorgehensweise: Implementieren einer Komponente, die das ereignisbasierte asynchrone Muster unterstützt.
Sofern Ihre Klasse die MethodNameAsync-Methode ohne den Zustandsparameter definiert und nicht mehrere, gleichzeitige Aufrufe unterstützt, stellen Sie sicher, dass jeder Versuch, MethodNameAsync aufzurufen, bevor der vorherige MethodNameAsync-Aufruf abgeschlossen wurde, eine InvalidOperationException auslöst.
Im Allgemeinen lösen Sie aber keine Ausnahme aus, wenn die MethodNameAsync-Methode ohne den userSuppliedState-Parameter mehrmals aufgerufen wird, sodass mehrere ausstehende Operationen vorhanden sind. Lösen Sie ggf. eine Ausnahme aus, wenn die Klasse explizit diesen Sachverhalt nicht bewältigen kann. Gehen Sie aber davon aus, dass Entwickler mit diesen mehrfachen, nicht differenzierbaren Rückrufen umgehen können.
Zugreifen auf Ergebnisse
Wenn während der Ausführung der asynchronen Operation ein Fehler aufgetreten ist, sollte auf die Ergebnisse nicht zugegriffen werden können. Stellen Sie sicher, dass der Zugriff auf jede Eigenschaft von AsyncCompletedEventArgs die Ausnahme auslöst, auf die Error verweist, wenn Error nicht null ist. Für diesen Zweck stellt die AsyncCompletedEventArgs-Klasse die RaiseExceptionIfNecessary-Methode bereit.
Stellen Sie sicher, dass jeder Versuch, auf das Ergebnis zuzugreifen, eine InvalidOperationException auslöst, die auf den Abbruch der Operation hinweist. Verwenden Sie hierzu die AsyncCompletedEventArgs.RaiseExceptionIfNecessary-Methode.
Fortschrittsberichterstellung
Unterstützen Sie, wenn möglich, die Fortschrittsberichterstellung. Dadurch können Entwickler bei der Verwendung Ihrer Klasse eine verbesserte Benutzerfreundlichkeit in Bezug auf die Anwendung erzielen.
Wenn Sie ein ProgressChanged/MethodNameProgressChanged-Ereignis implementieren, vergewissern Sie sich, dass keine derartigen Ereignisse für eine bestimmte asynchrone Operation ausgelöst werden, nachdem das MethodNameCompleted-Ereignis dieser Operation aufgerufen wurde.
Wenn das Standard-ProgressChangedEventArgs gefüllt wird, stellen Sie sicher, dass ProgressPercentage immer als Prozentsatz gelesen werden kann. Dieser Prozentsatz soll nicht exakt sein, sondern nur ein ungefähres Verhältnis widerspiegeln. Falls die Maßeinheit für die Fortschrittsberichterstellung kein Prozentsatz sein darf, leiten Sie von der ProgressChangedEventArgs-Klasse eine Klasse ab und lassen ProgressPercentage bei 0 (null). Verwenden Sie für die Fortschrittsberichterstellung keine andere Maßeinheit.
Stellen Sie sicher, dass das ProgressChanged-Ereignis im entsprechenden Thread und in Bezug auf die Lebensdauer der Anwendung zum richtigen Zeitpunkt ausgelöst wird. Weitere Informationen finden Sie im Abschnitt über Threading und Kontexte.
IsBusy-Implementierung
Machen Sie keine IsBusy-Eigenschaft verfügbar, wenn von der Klasse mehrere, gleichzeitige Aufrufe unterstützt werden. Beispielsweise stellen XML-Webdienstproxys keine IsBusy-Eigenschaft zur Verfügung, da von ihnen mehrere, gleichzeitige Aufrufe asynchroner Methoden unterstützt werden.
Die IsBusy-Eigenschaft sollte true zurückgeben, nachdem die MethodNameAsync-Methode aufgerufen wurde und bevor das MethodNameCompleted-Ereignis ausgelöst wurde. Andernfalls sollte sie false zurückgeben. Die BackgroundWorker-Komponente und die WebClient-Komponente sind Beispiele für Klassen, die eine IsBusy-Eigenschaft verfügbar machen.
Abbruch
Unterstützen Sie den Abbruch, falls möglich. Dadurch können Entwickler bei der Verwendung der Klasse eine verbesserte Benutzerfreundlichkeit in Bezug auf die Anwendung erzielen.
Legen Sie im Fall eines Abbruchs das Cancelled-Flag im AsyncCompletedEventArgs-Objekt fest.
Stellen Sie sicher, dass jeder Versuch, auf das Ergebnis zuzugreifen, eine InvalidOperationException auslöst, die auf den Abbruch der Operation hinweist. Verwenden Sie hierzu die AsyncCompletedEventArgs.RaiseExceptionIfNecessary-Methode.
Stellen Sie sicher, dass Aufrufe einer Abbruchmethode immer erfolgreich beendet werden können und niemals eine Ausnahme auslösen. Ein Client wird im Allgemeinen nicht darüber benachrichtigt, ob eine Operation tatsächlich zu einem beliebigen Zeitpunkt abgebrochen werden kann. Er wird auch nicht darüber in Kenntnis gesetzt, ob ein zuvor vorgenommener Abbruch erfolgreich ausgeführt wurde. Die Anwendung hingegen wird stets über einen erfolgreich ausgeführten Abbruch benachrichtigt, da sie am Abschlussstatus mitwirkt.
Lösen Sie bei Abbruch der Operation das MethodNameCompleted-Ereignis aus.
Fehler und Ausnahmen
- Fangen Sie jede in der asynchronen Operation auftretende Ausnahme ab, und legen Sie den Wert der AsyncCompletedEventArgs.Error-Eigenschaft auf diese Ausnahme fest.
Threading und Kontexte
Für das richtige Funktionieren der Klasse ist es entscheidend, dass die Ereignishandler des Clients in Bezug auf das entsprechende Anwendungsmodell, einschließlich ASP.NET und Anwendungen von Windows Forms im passenden Thread oder Kontext aufgerufen werden. Um sicherzustellen, dass die asynchrone Klasse unter jedem beliebigen Anwendungsmodell richtig funktioniert, werden zwei wichtige Hilfsklassen bereitgestellt: AsyncOperation und AsyncOperationManager.
AsyncOperationManager stellt eine Methode bereit, CreateOperation, die eine AsyncOperation zurückgibt. Die MethodNameAsync-Methode ruft CreateOperation auf, und die Klasse verwendet die zurückgegebene AsyncOperation, um die Lebensdauer der asynchronen Aufgabe zu verfolgen.
Um den Client über Fortschritte, inkrementelle Ergebnisse und den Abschluss zu benachrichtigen, rufen Sie die Post-Methode und die OperationCompleted-Methode für die AsyncOperation auf. AsyncOperation ist für Marshallingaufrufe an die Ereignishandler des Clients im passenden Thread oder Kontext verantwortlich.
Hinweis |
---|
Sie können diese Regeln umgehen, wenn Sie sich explizit nicht an die Richtlinien des Anwendungsmodells halten möchten, aber dennoch auf die übrigen Vorteile des ereignisbasierten asynchronen Musters nicht verzichten möchten.Unter Umständen haben Sie beispielsweise den Wunsch, dass eine Klasse in Windows Forms mit freiem Thread arbeitet.Sie können eine Klasse mit freiem Thread erzeugen, solange Entwickler über die damit einhergehenden Beschränkungen Bescheid wissen.Konsolenanwendungen synchronisieren nicht die Ausführung von Post-Aufrufen.Dies kann dazu führen, dass ProgressChanged-Ereignisse nicht in der richtigen Reihenfolge ausgelöst werden.Wenn Sie möchten, dass Post-Aufrufe nacheinander ausgeführt werden, implementieren und installieren Sie eine System.Threading.SynchronizationContext-Klasse. |
Weitere Informationen zum Verwenden von AsyncOperation und AsyncOperationManager für asynchrone Operationen finden Sie unter Exemplarische Vorgehensweise: Implementieren einer Komponente, die das ereignisbasierte asynchrone Muster unterstützt.
Richtlinien
Im Idealfall sollte jeder Methodenaufruf von weiteren Aufrufen unabhängig sein. Sie sollten es vermeiden, Aufrufe zu koppeln, die die gleichen Ressourcen nutzen. Sofern Ressourcen von Aufrufen gemeinsam genutzt werden sollen, müssen Sie bei der Implementierung einen geeigneten Synchronisierungsvorgang bereitstellen.
Von Entwürfen, bei denen der Client die Synchronisierung implementieren muss, ist abzuraten. Angenommen, Sie besäßen eine asynchrone Methode, die ein globales, statisches Objekt als Parameter erhält. Mehrere, gleichzeitige Aufrufe einer solchen Methode hätten dann eine Beschädigung der Daten oder einen Deadlock zur Folge.
Wenn Sie eine Methode mit der Mehrfachaufrufüberladung implementieren (userState in der Signatur), dann muss Ihre Klasse mehrere Benutzerzustände oder Aufgaben-IDs mit den zugehörigen, ausstehenden Operationen verwalten. Diese Auflistung sollte mit lock-Bereichen geschützt werden, da dieser Auflistung durch die zahlreichen Aufrufe userState-Objekte entweder hinzugefügt oder aber aus ihr entfernt werden.
Ziehen Sie die Wiederverwendung von CompletedEventArgs-Klassen in Erwägung, sofern dies machbar ist und angemessen erscheint. In einem solchen Fall ist die Benennung in Bezug auf den Methodennamen nicht konsistent, da ein gegebener Delegat sowie der EventArgs-Typ nicht an eine einzelne Methode gebunden sind. Jedoch ist es keinesfalls akzeptabel, dass die Entwickler dazu gezwungen werden, den von einer Eigenschaft im EventArgs abgerufenen Wert umzuwandeln.
Wenn Sie eine Klasse erstellen, die von Component abgeleitet ist, implementieren und installieren Sie keine eigene SynchronizationContext-Klasse. Anwendungsmodelle, nicht Komponenten, steuern den verwendeten SynchronizationContext.
Wenn Sie irgendeine Form des Multithreading verwenden, sind Sie immer auch der Gefahr ausgesetzt, schwerwiegende und komplexe Fehler zu begehen. Konsultieren Sie daher vor dem Implementieren einer beliebigen Lösung Empfohlene Vorgehensweise für das verwaltete Threading.
Siehe auch
Aufgaben
Gewusst wie: Verwenden von Komponenten, die das ereignisbasierte asynchrone Muster unterstützen
Referenz
Konzepte
Implementieren des ereignisbasierten asynchronen Entwurfsmusters
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