Freigeben über


Übersicht über ereignisbasiertes asynchrones Muster

Anwendungen, die viele Aufgaben gleichzeitig ausführen und dennoch reaktionsfähig bleiben, erfordern häufig ein Design, das mehrere Threads verwendet. Der System.Threading Namespace stellt alle Tools bereit, die zum Erstellen leistungsstarker Multithread-Anwendungen erforderlich sind. Die Verwendung dieser Tools erfordert jedoch eine erhebliche Erfahrung mit Multithreading-Softwaretechnik. Für relativ einfache Multithread-Anwendungen bietet die BackgroundWorker Komponente eine einfache Lösung. Für komplexere asynchrone Anwendungen sollten Sie eine Klasse implementieren, die dem ereignisbasierten asynchronen Muster entspricht.

Das ereignisbasierte asynchrone Muster stellt die Vorteile von Multithread-Anwendungen zur Verfügung, während viele der komplexen Probleme ausgeblendet werden, die im Multithread-Design inhärent sind. Die Verwendung einer Klasse, die dieses Muster unterstützt, kann Folgendes ermöglichen:

  • Führen Sie zeitaufwendige Aufgaben aus, z. B. Downloads und Datenbankvorgänge, "im Hintergrund", ohne Ihre Anwendung zu unterbrechen.

  • Führen Sie mehrere Vorgänge gleichzeitig aus, und empfangen Sie Benachrichtigungen, wenn die einzelnen Vorgänge abgeschlossen sind.

  • Warten Sie, bis Ressourcen verfügbar sind, ohne Ihre Anwendung zu blockieren ("stoppen").

  • Kommunizieren Sie mit ausstehenden asynchronen Vorgängen mithilfe des vertrauten Ereignis- und Delegatenmodells. Weitere Informationen zur Verwendung von Ereignishandlern und Delegaten finden Sie unter "Ereignisse".

Eine Klasse, die das ereignisbasierte asynchrone Muster unterstützt, verfügt über eine oder mehrere Methoden namens MethodNameAsync. Diese Methoden können synchrone Versionen spiegeln, die denselben Vorgang für den aktuellen Thread ausführen. Die Klasse kann auch über ein MethodNameCompleted-Ereignis verfügen und eine MethodNameAsyncCancel (oder einfach CancelAsync)-Methode haben.

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

Wenn Ihre Anwendung während des Ladens des Images weiterhin ausgeführt werden soll, können Sie die LoadAsync Methode aufrufen und das LoadCompleted Ereignis behandeln, genau wie jedes andere Ereignis. Wenn Sie die LoadAsync Methode aufrufen, wird die Anwendung weiterhin ausgeführt, während der Download in einem separaten Thread ("im Hintergrund") fortgesetzt wird. Der Ereignishandler wird aufgerufen, wenn der Imageladevorgang abgeschlossen ist, und der Ereignishandler kann den AsyncCompletedEventArgs Parameter untersuchen, um zu ermitteln, ob der Download erfolgreich abgeschlossen wurde.

Für das ereignisbasierte asynchrone Muster muss ein asynchroner Vorgang abgebrochen werden, und das PictureBox Steuerelement unterstützt diese Anforderung mit seiner CancelAsync Methode. Durch Aufrufen CancelAsync wird eine Anforderung gesendet, um den ausstehenden Download zu beenden, und wenn die Aufgabe abgebrochen wird, wird das LoadCompleted Ereignis ausgelöst.

Vorsicht

Es ist möglich, dass der Download so abgeschlossen wird, wie die CancelAsync Anforderung gestellt wird. Dies kann also Cancelled nicht die Anforderung zum Abbrechen widerspiegeln. Dies wird als Racebedingung bezeichnet und ist ein häufiges Problem bei der Multithread-Programmierung. Weitere Informationen zu Problemen bei der Multithread-Programmierung finden Sie unter "Bewährte Methoden für verwaltete Threading".

Merkmale des ereignisbasierten asynchronen Musters

Das ereignisbasierte asynchrone Muster kann verschiedene Formen annehmen, abhängig von der Komplexität der Vorgänge, die von einer bestimmten Klasse unterstützt werden. Die einfachsten Klassen verfügen möglicherweise über eine einzelne MethodNameAsync-Methode und ein entsprechendes MethodNameCompleted-Ereignis . Komplexere Klassen können mehrere MethodNameAsync-Methoden aufweisen, jeweils mit 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 auch mehrere ausstehende Aufrufe (mehrere gleichzeitige Aufrufe) unterstützen, sodass Ihr Code sie beliebig oft aufrufen kann, bevor andere ausstehende Vorgänge abgeschlossen werden. Die ordnungsgemäße Behandlung dieser Situation erfordert möglicherweise, dass Ihre Anwendung den Abschluss der einzelnen Vorgänge nachverfolgt.

Beispiele für das ereignisbasierte asynchrone Muster

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

Nachfolgend sehen Sie eine Beispielklassendeklaration, 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 synchrone und asynchrone Aufrufe unterstützen. Die synchronen Überladungen verhalten sich wie jeder Methodenaufruf und führen den Vorgang im aufrufenden Thread aus. wenn der Vorgang zeitaufwändig ist, kann es eine spürbare Verzögerung geben, bevor der Aufruf zurückgegeben wird. Die asynchronen Überladungen starten den Vorgang in einem anderen Thread und kehren dann sofort zurück, sodass der aufrufende Thread weiterarbeiten 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 diese beiden Formen anhand ihrer Methodensignaturen unterscheiden: Die Mehrfachaufrufform hat einen zusätzlichen Parameter, genannt 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) vor Abschluss eines vorherigen Aufrufs aufzurufen, löst die Methode eine InvalidOperationException aus.

Mit userState dem Parameter für die Mehrfachaufrufüberladungen können Sie zwischen asynchronen Vorgängen unterscheiden. Sie stellen einen eindeutigen Wert (z. B. eine GUID oder einen Hashcode) für jeden Aufruf Method1Async(string param, object userState)bereit, und wenn jeder Vorgang abgeschlossen ist, kann der 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 Method1Async(string param, object userState)generieren Sie in der Regel ein neues, eindeutiges userState Objekt und fügen es einer Auflistung hinzu. Wenn die Aufgabe, die diesem userState Objekt entspricht, das Abschlussereignis auslöst, wird Ihre Implementierung der Abschlussmethode AsyncCompletedEventArgs.UserState prüfen und es aus Ihrer Auflistung entfernen. Auf diese Weise verwendet, übernimmt der userState Parameter 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, asynchrone Vorgänge jederzeit vor Abschluss abzubrechen. 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, verwenden einen userState Parameter, der verwendet werden kann, um die Lebensdauer der einzelnen Vorgänge nachzuverfolgen. CancelAsync verwendet einen userState Parameter, mit dem Sie bestimmte ausstehende Vorgänge abbrechen können.

Methoden, die nur einen einzelnen ausstehenden Vorgang gleichzeitig unterstützen, wie Method1Async(string param)z. B. , können nicht abgebrochen werden.

Erhalten von Statusaktualisierungen und inkrementellen Ergebnissen

Eine Klasse, die dem ereignisbasierten asynchronen Muster entspricht, kann optional ein Ereignis zum Nachverfolgen des Status und der inkrementellen Ergebnisse bereitstellen. Dies wird in der Regel benannt ProgressChanged oder MethodNameProgressChanged, und der entsprechende Ereignishandler verwendet einen ProgressChangedEventArgs Parameter.

Der Ereignishandler für das ProgressChanged Ereignis kann die ProgressChangedEventArgs.ProgressPercentage Eigenschaft untersuchen, um zu bestimmen, welcher Prozentsatz einer asynchronen Aufgabe abgeschlossen wurde. Diese Eigenschaft reicht von 0 bis 100 und kann verwendet werden, um die Value-Eigenschaft eines ProgressBar zu aktualisieren. Wenn mehrere asynchrone Vorgänge ausstehen, können Sie mithilfe der ProgressChangedEventArgs.UserState Eigenschaft unterscheiden, welcher Vorgang den Fortschritt meldet.

Einige Klassen können inkrementelle Ergebnisse melden, während asynchrone Vorgänge ausgeführt werden. Diese Ergebnisse werden in einer Klasse gespeichert, von ProgressChangedEventArgs der sie abgeleitet wird, und sie werden als Eigenschaften in der abgeleiteten Klasse angezeigt. Sie können auf diese Ergebnisse im Ereignishandler für das ProgressChanged Ereignis zugreifen, genau wie sie auf die ProgressPercentage Eigenschaft zugreifen würden. Wenn mehrere asynchrone Vorgänge ausstehen, können Sie mithilfe der UserState Eigenschaft unterscheiden, welcher Vorgang inkrementelle Ergebnisse meldet.

Siehe auch