Übersicht über ereignisbasierte asynchrone Muster

Für Anwendungen, die viele Aufgaben gleichzeitig durchführen, aber weiterhin auf Benutzerinteraktionen reagieren, ist oft ein Entwurf erforderlich, der mehrere Threads verwendet. Der System.Threading-Namespace bietet alle erforderlichen Tools für die Erstellung von leistungsstarken Multithreadanwendungen, aber für eine effektive Verwendung dieser Tools ist eine umfassende Erfahrung mit der Multithread-Softwareentwicklung erforderlich. Für relativ einfache Multithreadanwendungen bietet die BackgroundWorker-Komponente eine unkomplizierte Lösung. Für komplexere asynchrone Anwendungen sollten Sie die Implementierung einer Klasse in Betracht ziehen, die den ereignisbasierten asynchronen Muster entspricht.

Das ereignisbasierte asynchrone Muster stellt die Vorteile von Multithreadanwendungen zur Verfügung und blendet gleichzeitig viele komplexe Aspekte von Multithreadentwürfen aus. Die Verwendung einer Klasse, die dieses Muster unterstützt, ermöglicht Folgendes:

  • Führen Sie zeitaufwendige Aufgaben wie Downloads und Datenbankvorgänge „in Hintergrund“ aus, ohne Ihre Anwendung zu unterbrechen.

  • Führen Sie mehrere Vorgänge gleichzeitig aus, und erhalten Sie Benachrichtigungen, wenn jede abgeschlossen ist.

  • Warten Sie, bis Ressourcen verfügbar werden, ohne Ihre Anwendung anzuhalten („blockieren“).

  • Kommunizieren Sie mit ausstehenden asynchronen Vorgängen über das vertraute Modell mit Ereignissen und Delegaten. Weitere Informationen zum Verwenden von Ereignishandlern und Delegaten finden Sie unter Ereignisse.

Eine Klasse, die das ereignisbasierte asynchrone Muster unterstützt, verfügt über mindesten eine Methode namens MethodNameAsync. Diese Methoden spiegeln möglicherweise synchrone Versionen wider, die denselben Vorgang im aktuellen Thread durchführen. Die Klasse kann darüber hinaus auch über ein MethodNameCompleted-Ereignis und eine MethodNameAsyncCancel-Methode (kurz CancelAsync) verfügen.

PictureBox ist eine typische Komponente, die das ereignisbasierte asynchrone Muster unterstützt. Sie können ein Image synchron herunterladen, indem Sie seine Load-Methode aufrufen, aber wenn das Image groß oder die Netzwerkverbindung langsam ist, reagiert Ihre Anwendung nicht mehr, bis der Downloadvorgang abgeschlossen ist und der Aufruf an Load zurückgegeben wird.

Wenn Sie möchten, dass Ihre Anwendung während des Ladens des Images weiter ausgeführt wird, können Sie die LoadAsync-Methode aufrufen und das LoadCompleted-Ereignis verarbeiten, wie Sie jedes andere Ereignis verarbeiten würden. Wenn Sie die LoadAsync-Methode aufrufen, wird Ihre Anwendung weiterhin ausgeführt, während der Download in einem separaten Thread („im Hintergrund“) fortgesetzt wird. Ihr Ereignishandler wird aufgerufen, wenn der Ladevorgang für das Image abgeschlossen ist, und Ihr Ereignishandler kann den Parameter AsyncCompletedEventArgs untersuchen, um festzustellen, ob der Download erfolgreich abgeschlossen wurde.

Das ereignisbasierte asynchrone Muster erfordert, dass ein asynchroner Vorgang abgebrochen werden kann, und das PictureBox-Steuerelement unterstützt diese Anforderung mit der CancelAsync-Methode. Bei Aufrufen von CancelAsync wird eine Anforderung übermittelt, um den ausstehenden Download zu beenden. Wenn die Aufgabe abgebrochen wird, wird das LoadCompleted-Ereignis ausgelöst.

Achtung

Es ist möglich, dass der Download gerade in dem Moment abgeschlossen wird, in dem die CancelAsync-Anforderung erfolgt, sodass Cancelled die Anforderung zum Abbrechen möglicherweise nicht widerspiegelt. Dies wird als Racebedingung bezeichnet und ist ein typisches Problem bei der Multithreadprogrammierung. Weitere Informationen zu Problemen bei der Multithreadprogrammierung finden Sie unter Empfohlene Vorgehensweise für das verwaltete Threading.

Eigenschaften des ereignisbasierten asynchronen Musters

Das ereignisbasierte asynchrone Muster kann je nach Komplexität der von einer bestimmten Klasse unterstützten Vorgänge mehrere Formen annehmen. Die einfachsten Klassen verfügen möglicherweise über eine einzige MethodNameAsync-Methode und ein entsprechendes MethodNameCompleted-Ereignis. Komplexere Klassen verfügen möglicherweise über mehrere MethodNameAsync-Methoden mit jeweils einem entsprechenden MethodNameCompleted-Ereignis sowie synchronen Versionen dieser Methoden. Klassen können optional das Abbrechen, die Statusberichterstellung und inkrementelle Ergebnisse für jede asynchrone Methode unterstützen.

Eine asynchrone Methode kann außerdem mehrere ausstehende Aufrufe (mehrere parallele Aufrufe) unterstützen, sodass Ihr Code diese beliebig oft aufrufen kann, bevor andere ausstehende Vorgänge abgeschlossen werden. Für eine ordnungsgemäße Handhabung dieser Situation muss Ihre Anwendung möglicherweise den Abschluss jedes Vorgangs nachverfolgen.

Beispiele für das ereignisbasierte asynchrone Muster

Die Komponenten SoundPlayer und PictureBox stellen einfache Implementierungen des ereignisbasierten asynchronen Musters dar. Die Komponenten WebClient und BackgroundWorker stellen komplexere Implementierungen des ereignisbasierten asynchronen Musters dar.

Im Folgenden sehen Sie ein Beispiel für eine Klassendeklaration, die dem 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 verfügt über zwei Methoden, die beide synchrone und asynchrone Aufrufe unterstützen. Die synchronen Überladungen verhalten sich wie jeder beliebige Methodenaufruf und führen den Vorgang im aufrufenden Thread aus. Wenn der Vorgang zeitaufwendig ist, kommt es möglicherweise zu einer spürbaren Verzögerung, bis der Aufruf zurückgegeben wird. Die asynchronen Überlastungen starten den Vorgang in einem anderen Thread und geben dann sofort zurück, sodass der aufrufende Thread fortgesetzt werden kann, während der Vorgang „im Hintergrund“ ausgeführt wird.

Asynchrone Methodenüberladungen

Es gibt potenziell zwei Überladungen für die asynchronen Vorgänge: einzelner Aufruf und mehrere Aufrufe. Sie können die zwei Formen anhand ihrer Methodensignaturen unterscheiden: Die Form mit mehreren Aufrufen hat einen zusätzlichen Parameter namens userState. Diese Form ermöglicht Ihrem Code, Method1Async(string param, object userState) mehrmals aufzurufen, ohne darauf warten zu müssen, dass ausstehende asynchrone Vorgänge abgeschlossen werden. Wenn Sie andererseits versuchen, Method1Async(string param) aufzurufen, bevor ein vorheriger Aufruf abgeschlossen ist, löst die Methode eine InvalidOperationException aus.

Der Parameter userState für die Überladungen mit mehreren Aufrufen ermöglicht Ihnen, zwischen asynchronen Vorgängen zu unterscheiden. Sie stellen einen eindeutigen Wert (z.B. eine GUID oder einen Hashcode) für jeden Aufruf an Method1Async(string param, object userState) bereit. Wenn jeder Vorgang abgeschlossen ist, kann Ihr Ereignishandler bestimmen, welche Instanz des Vorgangs das Abschlussereignis ausgelöst hat.

Nachverfolgen ausstehender Vorgänge

Wenn Sie die Überladungen mit mehreren Aufrufen verwenden, muss Ihr Code die userState-Objekte (Aufgaben-IDs) für ausstehende Aufgaben nachverfolgen. Für jeden Aufruf an Method1Async(string param, object userState) generieren Sie in der Regel ein neues eindeutiges userState-Objekt und fügen es einer Auflistung hinzu. Wenn die diesem userState-Objekt entsprechende Aufgabe das Abschlussereignis auslöst, untersucht Ihre Abschlussmethodenimplementierung AsyncCompletedEventArgs.UserState und entfernt es aus Ihrer Auflistung. Auf diese Weise verwendet übernimmt der Parameter userState die Rolle einer Aufgaben-ID.

Hinweis

Sie müssen darauf achten, einen eindeutigen Wert für userState in Ihren Aufrufen an Überladungen mit mehreren Aufrufen bereitzustellen. Nicht eindeutige Aufgaben-IDs führen dazu, dass die asynchrone Klasse eine ArgumentException auslöst.

Abbrechen ausstehender Vorgänge

Es ist wichtig, dass asynchrone Vorgänge jederzeit vor Ihrem Abschluss abgebrochen werden können. Klassen, die das ereignisbasierte asynchrone Muster implementieren, verfügen über eine CancelAsync-Methode (wenn nur eine asynchrone Methode vorhanden ist) oder eine MethodNameAsyncCancel-Methode (wenn mehrere asynchrone Methoden vorhanden sind).

Methoden, die mehrere Aufrufe zulassen, nehmen einen userState-Parameter an, der verwendet werden kann, um die Lebensdauer jeder Aufgabe nachzuverfolgen. CancelAsync nimmt einen userState-Parameter an, mit dem Sie bestimmte ausstehende Aufgaben abbrechen können.

Methoden, die nur einen einzigen ausstehenden Vorgang auf einmal unterstützen, wie Method1Async(string param), können nicht abgebrochen werden.

Erhalten von Statusaktualisierungen und inkrementellen Ergebnissen

Eine Klasse, die dem ereignisbasierten asynchronen Muster entspricht, kann optional ein Ereignis für die Statusverfolgung und inkrementelle Ergebnisse bereitstellen. Diese wird normalerweise ProgressChanged oder MethodNameProgressChanged genannt, und ihr entsprechender Ereignishandler nimmt den Parameter ProgressChangedEventArgs an.

Der Ereignishandler für das ProgressChanged-Ereignis kann die Eigenschaft ProgressChangedEventArgs.ProgressPercentage untersuchen, um festzustellen, welcher Prozentsatz eines asynchronen Tasks abgeschlossen wurde. Diese Eigenschaft liegt zwischen 0 und 100 und kann verwendet werden, um die Value-Eigenschaft von ProgressBar zu aktualisieren. Wenn mehrere asynchrone Vorgänge ausstehen, können Sie die Eigenschaft ProgressChangedEventArgs.UserState verwenden, um zu unterscheiden, welcher Vorgang den Status meldet.

Einige Klassen melden möglicherweise inkrementelle Ergebnisse, wenn asynchrone Vorgänge fortgesetzt werden. Diese Ergebnisse werden in einer Klasse gespeichert, die von ProgressChangedEventArgs abgeleitet ist und als Eigenschaften in der abgeleiteten Klasse angezeigt. Sie können auf diese Ergebnisse im Ereignishandler für das ProgressChanged-Ereignis so zugreifen, wie Sie auf die ProgressPercentage-Eigenschaft zugreifen würden. Wenn mehrere asynchrone Vorgänge ausstehen, können Sie die Eigenschaft UserState verwenden, um zu unterscheiden, welcher Vorgang inkrementelle Ergebnisse meldet.

Siehe auch