Übersicht über ereignisbasierte asynchrone Muster

Aktualisiert: November 2007

Für Anwendungen, die mehrere Aufgaben gleichzeitig ausführen, aber weiterhin auf Benutzerinteraktionen reagieren, ist oftmals ein besonderes Design erforderlich, das die Verwendung mehrerer Threads erlaubt. Vom System.Threading-Namespace werden alle für die Erstellung von leistungsfähigen Multithreadanwendungen benötigten Tools bereitgestellt. Für den effizienten Einsatz dieser Tools ist jedoch eine gewisse Erfahrung im Umgang mit der Multithread-Softwareentwicklung erforderlich. Bei relativ einfachen Multithreadanwendungen hält die BackgroundWorker-Komponente eine unkomplizierte Lösung parat. Bei ausgereifteren asynchronen Anwendungen ist das Implementieren einer Klasse zu empfehlen, die sich nach dem ereignisbasierten asynchronen Entwurfsmuster orientiert.

Über dieses Muster können bei gleichzeitiger Nutzung der Vorteile von Multithreadanwendungen viele der komplexeren Aspekte eines Multithreaddesigns außer Acht gelassen werden. Wenn Sie eine Klasse verwenden, die dieses Muster unterstützt, haben Sie folgende Möglichkeiten:

  • Ausführen zeitaufwändiger Tasks wie Downloads oder Datenbankoperationen "im Hintergrund", d. h. ohne Unterbrechung der Anwendung

  • Gleichzeitiges Ausführen mehrerer Operationen und Empfang von Benachrichtigungen bei Abschluss jeder einzelnen Operation

  • Warten auf das Freiwerden von Ressourcen ohne Beenden ("Hängenbleiben") der Anwendung

  • Kommunizieren mit ausstehenden asynchronen Operationen unter Verwendung des vertrauten Ereignis- und Delegatmodells. Weitere Informationen zur Verwendung von Ereignishandlern und Delegaten finden Sie unter Ereignisse und Delegaten.

Eine Klasse, die das ereignisbasierte asynchrone Muster unterstützt, besitzt eine oder mehrere Methoden mit dem Namen MethodNameAsync. Diese Methoden spiegeln möglicherweise synchrone Versionen, die auf dem aktuellen Thread die gleiche Operation ausführen. Die Klasse kann darüber hinaus auch ein MethodNameCompleted-Ereignis sowie eine MethodNameAsyncCancel-Methode (oder einfach CancelAsync) besitzen.

PictureBox ist eine typische Komponente, die das ereignisbasierte asynchrone Muster unterstützt. Sie können ein Bild synchron downloaden, wenn Sie dessen Load-Methode aufrufen. Wenn das Bild allerdings sehr groß ist oder nur eine langsame Netzwerkverbindung vorhanden ist, wird die Anwendung beendet (sie bleibt "hängen"), bis der Download abgeschlossen ist und der Aufruf von Load zurückgegeben wird.

Wenn die Anwendung während des Downloads weiter ausgeführt werden soll, dann können Sie die LoadAsync-Methode aufrufen und das LoadCompleted-Ereignis so wie jedes andere Ereignis behandeln. Wenn Sie die LoadAsync-Methode aufrufen, wird die Anwendung weiter ausgeführt, während der Download auf einem separaten Thread ("im Hintergrund") fortgesetzt wird. Ist das Bild vollständig geladen, wird der Ereignishandler aufgerufen, der dann den AsyncCompletedEventArgs-Parameter dahingehend überprüfen kann, ob der Download erfolgreich abgeschlossen wurde.

An das ereignisbasierte asynchrone Muster ist die Bedingung geknüpft, dass eine asynchrone Operation abgebrochen werden kann. Vom PictureBox-Steuerelement wird diese Bedingung mithilfe der CancelAsync-Methode unterstützt. Mit dem Aufrufen von CancelAsync wird das Beenden des ausstehenden Downloads angefordert. Nach Abbruch der Aufgabe wird dann das LoadCompleted-Ereignis ausgelöst.

Vorsicht:

Es besteht die Möglichkeit, dass der Download in dem Moment abgeschlossen wird, in dem eine CancelAsync-Anforderung erstellt wird. Daher entspricht Cancelled möglicherweise nicht der Anforderung für einen Abbruch. Dies wird als Racebedingung bezeichnet und stellt ein häufiges Problem bei der Programmierung mit Multithreading dar. Weitere Informationen zu Problemen bei der Programmierung mit Multithreading finden Sie unter Empfohlene Vorgehensweise für das verwaltete Threading.

Eigenschaften des ereignisbasierten asynchronen Entwurfsmusters

Das ereignisbasierte asynchrone Muster kann unterschiedliche Formate aufweisen, je nachdem wie komplex die von einer bestimmten Klasse unterstützten Operationen ausfallen. Die einfachsten Klassen verfügen möglicherweise nur über eine einzige MethodNameAsync-Methode und ein entsprechendes MethodNameCompleted-Ereignis. Komplexere Klassen können mehrere MethodNameAsync-Methoden zusammen mit dem jeweiligen MethodNameCompleted-Ereignis und außerdem synchrone Versionen dieser Methoden enthalten. Klassen können wahlweise für jede asynchrone Methode einen Abbruch, eine Statusmeldung und inkrementelle Ergebnisse unterstützen.

Zusätzlich unterstützt eine asynchrone Methode mehrere ausstehende Aufrufe (mehrere gleichzeitige Aufrufe). Dadurch kann die Methode vom Code beliebig oft aufgerufen werden, bevor weitere anstehende Operationen zum Abschluss gebracht werden. Damit die Anwendung diese Situation meistern kann, muss sie ggf. den Abschluss jeder einzelnen Operation verfolgen.

Eigenschaften des ereignisbasierten asynchronen Musters

Die SoundPlayer-Komponente und die PictureBox-Komponente stellen einfache Implementierungen des ereignisbasierten asynchronen Musters dar. Die WebClient-Komponente und die BackgroundWorker-Komponente stellen demgegenüber komplexere Implementierungen des ereignisbasierten asynchronen Musters dar.

Im Folgenden finden Sie ein Beispiel für eine Klassendeklaration, die diesem Muster entspricht:

Public Class AsyncExample
    ' Synchronous methods.
    Public Function Method1(ByVal param As String) As Integer 
    Public Sub Method2(ByVal param As Double) 

    ' Asynchronous methods.
    Overloads Public Sub Method1Async(ByVal param As String) 
    Overloads Public Sub Method1Async(ByVal param As String, ByVal userState As Object) 
    Public Event Method1Completed As Method1CompletedEventHandler

    Overloads Public Sub Method2Async(ByVal param As Double) 
    Overloads Public Sub Method2Async(ByVal param As Double, ByVal userState As Object) 
    Public Event Method2Completed As Method2CompletedEventHandler

    Public Sub CancelAsync(ByVal userState As Object) 

    Public ReadOnly Property IsBusy () As Boolean

    ' Class implementation not shown.
End Class
public class AsyncExample
{
    // Synchronous methods.
    public int Method1(string param);
    public void Method2(double param);

    // Asynchronous methods.
    public void Method1Async(string param);
    public void Method1Async(string param, object userState);
    public event Method1CompletedEventHandler Method1Completed;

    public void Method2Async(double param);
    public void Method2Async(double param, object userState);
    public event Method2CompletedEventHandler Method2Completed;

    public void CancelAsync(object userState);

    public bool IsBusy { get; }

    // Class implementation not shown.
}

Die fiktive AsyncExample-Klasse besitzt zwei Methoden, die beide synchrone und asynchrone Aufrufe unterstützen. Synchrone Überladungen sind in ihrem Verhalten mit beliebigen Methodenaufrufen vergleichbar und führen die Operation auf dem aufrufenden Thread aus. Sofern diese Operation einige Zeit in Anspruch nimmt, kann sich die Rückgabe des Aufrufs nennenswert verzögern. Asynchrone Überladungen starten den Vorgang auf einem anderen Thread und geben anschließend sofort einen Wert zurück. Dadurch kann der aufrufende Thread fortfahren, während die Operation "im Hintergrund" ausgeführt wird.

Asynchrone Methodenüberladungen

Für asynchrone Operationen gibt es potenziell zwei Überladungen: Einfachaufruf und Mehrfachaufruf. Sie können diese beiden Formate nach ihrer jeweiligen Methodensignatur unterscheiden: Das Format Mehrfachaufruf besitzt einen gesonderten Parameter mit dem Namen userState. Durch dieses Format kann der Code mehrmals Method1Async(string param, object userState) aufrufen, ohne auf die Beendigung ausstehender asynchroner Operationen warten zu müssen. Wenn Sie aber versuchen, noch vor Abschluss eines vorherigen Aufrufs Method1Async(string param) aufzurufen, löst die Methode eine InvalidOperationException aus.

Durch den userState-Parameter für die Mehrfachaufrufüberladungen haben Sie die Möglichkeit, asynchrone Operationen zu unterscheiden. Sie stellen für jeden Aufruf von Method1Async(string param, object userState) einen eindeutigen Wert (beispielsweise eine GUID oder einen Hashcode) bereit. Der Ereignishandler kann dann nach Abschluss jeder einzelnen Operation entscheiden, welche Instanz der Operation das Abschlussereignis ausgelöst hat.

Verfolgen von ausstehenden Operationen

Bei Verwendung von Mehrfachaufrufüberladungen muss der Code für ausstehende Aufgaben die userState-Objekte (Aufgaben-IDs) verfolgen. Bei jedem Aufruf von Method1Async(string param, object userState) generieren Sie dann im Regelfall ein neues, eindeutiges userState-Objekt und fügen dieses einer Auflistung hinzu. Wenn die Aufgabe, die diesem userState-Objekt entspricht, das Abschlussereignis auslöst, wird durch die Abschlussmethodenimplementierung AsyncCompletedEventArgs.UserState überprüft und aus der Auflistung entfernt. Auf diese Art und Weise übernimmt der userState-Parameter die Rolle einer Aufgaben-ID.

Hinweis:

Wenn Sie Mehrfachaufrufüberladungen aufrufen, ist bei der Bereitstellung eines eindeutigen Werts für userState Vorsicht geboten. Nicht eindeutige Aufgaben-IDs haben zur Folge, dass die asynchrone Klasse eine ArgumentException auslöst.

Abbrechen von ausstehenden Operationen

Die Fähigkeit, asynchrone Operationen jederzeit vor Abschluss abzubrechen, ist von großer Bedeutung. Klassen, die das ereignisbasierte asynchrone Muster implementieren, besitzen entweder eine CancelAsync-Methode (wenn mehrere asynchrone Methoden vorhanden sind) oder eine MethodNameAsyncCancel-Methode (wenn nur eine asynchrone Methode vorhanden ist).

Methoden, bei denen Mehrfachaufrufe möglich sind, weisen einen userState-Parameter auf, der zum Verfolgen der Lebensdauer jeder einzelnen Aufgabe verwendet werden kann. CancelAsync weist einen userState-Parameter auf, der Ihnen das Abbrechen einzelner ausstehender Aufgaben ermöglicht.

Bei Methoden, die jeweils nur eine einzelne ausstehende Operation unterstützen, beispielsweise Method1Async(string param), ist kein Abbruch möglich.

Empfangen von Statusaktualisierungen und inkrementellen Ergebnissen

Eine Klasse, die dem ereignisbasierten asynchronen Entwurfsmuster folgt, kann wahlweise ein Ereignis zum Verfolgen des Status und von inkrementellen Ergebnissen bereitstellen. Dieser Vorgang wird im Regelfall ProgressChanged oder MethodNameProgressChanged genannt, und der entsprechende Ereignishandler weist einen ProgressChangedEventArgs-Parameter auf.

Der Ereignishandler für das ProgressChanged-Ereignis hat die Möglichkeit, die ProgressChangedEventArgs.ProgressPercentage-Eigenschaft zu überprüfen, um festzustellen, wieviel von einer asynchronen Aufgabe prozentual bereits erledigt wurde. Diese Eigenschaft bewegt sich in einem Bereich von 0 bis 100 und kann dazu verwendet werden, um die Value-Eigenschaft einer ProgressBar zu aktualisieren. Sofern mehrere asynchrone Operationen ausstehen, können Sie die ProgressChangedEventArgs.UserState-Eigenschaft verwenden, um zu differenzieren, welche Operation einen Status meldet.

Einige Klassen melden möglicherweise inkrementelle Ergebnisse bei der Ausführung von asynchronen Operationen. Die Ergebnisse werden in einer Klasse gespeichert, die sich von ProgressChangedEventArgs ableitet, und erscheinen in dieser abgeleiteten Klasse als Eigenschaften. Sie können auf diese Ergebnisse im Ereignishandler für das ProgressChanged-Ereignis zugreifen, und zwar so, als würden Sie auf die ProgressPercentage-Eigenschaft zugreifen. Sofern mehrere asynchrone Operationen ausstehen, können Sie die UserState-Eigenschaft verwenden, um zu differenzieren, welche Operation inkrementelle Ergebnisse meldet.

Siehe auch

Aufgaben

Gewusst wie: Verwenden von Komponenten, die das ereignisbasierte asynchrone Muster unterstützen

Gewusst wie: Ausführen eines Vorgangs im Hintergrund

Gewusst wie: Implementieren eines Formulars, das eine Hintergrundoperation verwendet

Konzepte

Bewährte Verfahrensweisen für das Implementieren des ereignisbasierten asynchronen Entwurfsmusters

Gründe für das Implementieren des ereignisbasierten asynchronen Musters

Referenz

ProgressChangedEventArgs

BackgroundWorker

AsyncCompletedEventArgs

Weitere Ressourcen

Multithreadprogrammierung mit dem ereignisbasierten asynchronen Muster