Asynchroniczny wzorzec oparty na zdarzeniach — przegląd

Aplikacje, które wykonują wiele zadań jednocześnie, ale pozostają dynamiczne na interakcję użytkownika, często wymagają projektu, który używa wielu wątków. System.Threading Przestrzeń nazw udostępnia wszystkie narzędzia niezbędne do tworzenia aplikacji wielowątkowych o wysokiej wydajności, ale efektywne korzystanie z tych narzędzi wymaga znaczącego doświadczenia w inżynierii oprogramowania wielowątkowego. W przypadku stosunkowo prostych aplikacji BackgroundWorker wielowątkowych składnik udostępnia proste rozwiązanie. W przypadku bardziej zaawansowanych aplikacji asynchronicznych rozważ zaimplementowanie klasy zgodnej ze wzorcem asynchronicznym opartym na zdarzeniach.

Wzorzec asynchroniczny oparty na zdarzeniach udostępnia zalety aplikacji wielowątkowych, ukrywając jednocześnie wiele złożonych problemów związanych z projektowaniem wielowątkowym. Użycie klasy obsługującej ten wzorzec umożliwia:

  • Wykonywanie czasochłonnych zadań, takich jak pobieranie i operacje bazy danych, "w tle" bez przerywania działania aplikacji.

  • Wykonaj wiele operacji jednocześnie, odbierając powiadomienia po zakończeniu każdego z nich.

  • Poczekaj, aż zasoby staną się dostępne bez zatrzymywania ("blokowania") aplikacji.

  • Komunikacja z oczekującymi operacjami asynchronicznymi przy użyciu znanego modelu zdarzeń i delegatów. Aby uzyskać więcej informacji na temat korzystania z programów obsługi zdarzeń i delegatów, zobacz Zdarzenia.

Klasa, która obsługuje asynchroniczny wzorzec oparty na zdarzeniach, będzie miała co najmniej jedną metodę o nazwie MethodNameAsync. Te metody mogą dublowania wersji synchronicznych, które wykonują tę samą operację w bieżącym wątku. Klasa może również mieć zdarzenie MethodName Completed i może mieć metodę MethodNameAsyncCancel (lub po prostu CancelAsync).

PictureBox jest typowym składnikiem obsługującym asynchroniczny wzorzec oparty na zdarzeniach. Obraz można pobrać synchronicznie, wywołując jego Load metodę, ale jeśli obraz jest duży lub jeśli połączenie sieciowe działa wolno, aplikacja przestanie odpowiadać do momentu zakończenia operacji pobierania i wywołania do Load powrotu.

Jeśli chcesz, aby aplikacja nadal działać podczas ładowania obrazu, możesz wywołać LoadAsync metodę i obsłużyć LoadCompleted zdarzenie, tak samo jak w przypadku każdego innego zdarzenia. Po wywołaniu LoadAsync metody aplikacja będzie nadal działać, gdy pobieranie będzie kontynuowane w osobnym wątku ("w tle"). Procedura obsługi zdarzeń zostanie wywołana po zakończeniu operacji ładowania obrazu, a program obsługi zdarzeń może zbadać AsyncCompletedEventArgs parametr, aby określić, czy pobieranie zostało ukończone pomyślnie.

Wzorzec asynchroniczny oparty na zdarzeniach wymaga anulowania operacji asynchronicznej, a kontrolka PictureBox obsługuje to wymaganie przy użyciu metody CancelAsync . Wywołanie metody CancelAsync przesyła żądanie zatrzymania oczekującego pobrania, a po anulowaniu LoadCompleted zadania jest zgłaszane zdarzenie.

Uwaga

Istnieje możliwość, że pobieranie zakończy się tak samo jak CancelAsync żądanie, więc Cancelled może nie odzwierciedlać żądania anulowania. Jest to nazywane warunkiem wyścigu i jest typowym problemem w programowaniu wielowątkowym. Aby uzyskać więcej informacji na temat problemów z programowaniem wielowątkowym, zobacz Managed Threading Best Practices (Najlepsze rozwiązania dotyczące zarządzanych wątków).

Charakterystyka wzorca asynchronicznego opartego na zdarzeniach

Wzorzec asynchroniczny oparty na zdarzeniach może mieć kilka form, w zależności od złożoności operacji obsługiwanych przez określoną klasę. Najprostsze klasy mogą mieć jedną metodę MethodName Async i odpowiadające mu zdarzenie MethodNameCompleted. Bardziej złożone klasy mogą mieć kilka metod MethodName Async, z których każda ma odpowiednie zdarzenie MethodNameCompleted, a także synchroniczne wersje tych metod. Klasy mogą opcjonalnie obsługiwać anulowanie, raportowanie postępu i przyrostowe wyniki dla każdej metody asynchronicznej.

Metoda asynchroniczna może również obsługiwać wiele oczekujących wywołań (wiele współbieżnych wywołań), dzięki czemu kod może wywołać go dowolną liczbę razy, zanim ukończy inne oczekujące operacje. Prawidłowa obsługa tej sytuacji może wymagać od aplikacji śledzenia ukończenia każdej operacji.

Przykłady wzorca asynchronicznego opartego na zdarzeniach

Składniki SoundPlayer i PictureBox reprezentują proste implementacje wzorca asynchronicznego opartego na zdarzeniach. Składniki WebClient i BackgroundWorker reprezentują bardziej złożone implementacje wzorca asynchronicznego opartego na zdarzeniach.

Poniżej znajduje się przykładowa deklaracja klasy zgodna ze wzorcem:

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.  
}  

Fikcyjna AsyncExample klasa ma dwie metody, z których obie obsługują wywołania synchroniczne i asynchroniczne. Przeciążenia synchroniczne zachowują się jak każde wywołanie metody i wykonują operację w wątku wywołującym; Jeśli operacja jest czasochłonna, może wystąpić zauważalne opóźnienie przed zwróceniem wywołania. Przeciążenia asynchroniczne uruchomią operację w innym wątku, a następnie natychmiast powrócą, umożliwiając kontynuowanie wywoływania wątku podczas wykonywania operacji "w tle".

Przeciążenia metody asynchronicznej

Istnieją potencjalnie dwa przeciążenia operacji asynchronicznych: wywołanie jednokrotne i wywołanie wielokrotne. Te dwie formy można odróżnić za pomocą podpisów metod: formularz wywołania wielokrotnego ma dodatkowy parametr o nazwie userState. Ten formularz umożliwia wywołanie Method1Async(string param, object userState) kodu wiele razy bez oczekiwania na zakończenie oczekujących operacji asynchronicznych. Jeśli z drugiej strony próbujesz wywołać metodę Method1Async(string param) przed ukończeniem poprzedniego wywołania, metoda wywołuje metodę InvalidOperationException.

Parametr userState przeciążeń wielokrotnego wywołania umożliwia rozróżnienie między operacjami asynchronicznymi. Należy podać unikatową wartość (na przykład identyfikator GUID lub kod skrótu) dla każdego wywołania metody Method1Async(string param, object userState), a po zakończeniu każdej operacji program obsługi zdarzeń może określić, które wystąpienie operacji wywołało zdarzenie ukończenia.

Śledzenie oczekujących operacji

Jeśli używasz przeciążeń wielokrotnego wywołania, kod będzie musiał śledzić userState obiekty (identyfikatory zadań) dla oczekujących zadań. Dla każdego wywołania metody Method1Async(string param, object userState)zwykle wygenerujesz nowy, unikatowy userState obiekt i dodasz go do kolekcji. Gdy zadanie odpowiadające temu userState obiektowi zgłasza zdarzenie ukończenia, implementacja metody ukończenia zbada AsyncCompletedEventArgs.UserState i usunie je z kolekcji. W ten sposób userState parametr przyjmuje rolę identyfikatora zadania.

Uwaga

Należy zachować ostrożność, aby podać unikatową wartość w userState wywołaniach przeciążeń wielokrotnego wywołania. Identyfikatory zadań innych niż unikatowe spowodują, że klasa asynchroniczna zgłosi błąd ArgumentException.

Anulowanie oczekujących operacji

Ważne jest, aby móc anulować operacje asynchroniczne w dowolnym momencie przed ich ukończeniem. Klasy implementujące asynchroniczny wzorzec oparty na zdarzeniach będą miały metodę CancelAsync (jeśli istnieje tylko jedna metoda asynchroniczna) lub metoda MethodNameAsyncCancel (jeśli istnieje wiele metod asynchronicznych).

Metody, które zezwalają na wiele wywołań, przyjmują userState parametr, który może służyć do śledzenia okresu istnienia każdego zadania. CancelAsyncuserState przyjmuje parametr , który umożliwia anulowanie określonych oczekujących zadań.

Metody, które obsługują tylko jedną oczekującą operację naraz, na przykład Method1Async(string param), nie można anulować.

Odbieranie postępów Aktualizacje i wyników przyrostowych

Klasa zgodna ze wzorcem asynchronicznym opartym na zdarzeniach może opcjonalnie dostarczyć zdarzenie do śledzenia postępu i wyników przyrostowych. Zazwyczaj będzie to nazwane ProgressChanged lub MethodNameProgressChanged, a odpowiednia procedura obsługi zdarzeń będzie przyjmować ProgressChangedEventArgs parametr.

Procedura obsługi zdarzeń dla ProgressChanged zdarzenia może zbadać ProgressChangedEventArgs.ProgressPercentage właściwość, aby określić procent wykonania zadania asynchronicznego. Ta właściwość będzie mieścić się w zakresie od 0 do 100 i może służyć do aktualizowania Value właściwości ProgressBarobiektu . Jeśli oczekują wiele operacji asynchronicznych, możesz użyć ProgressChangedEventArgs.UserState właściwości , aby odróżnić, która operacja zgłasza postęp.

Niektóre klasy mogą zgłaszać wyniki przyrostowe jako operacje asynchroniczne. Te wyniki będą przechowywane w klasie pochodzącej z ProgressChangedEventArgs klasy i będą wyświetlane jako właściwości w klasie pochodnej. Dostęp do tych wyników można uzyskać w procedurze obsługi zdarzeń dla ProgressChanged zdarzenia, tak jak w przypadku uzyskiwania ProgressPercentage dostępu do właściwości. Jeśli oczekują wiele operacji asynchronicznych, możesz użyć UserState właściwości , aby odróżnić, która operacja zgłasza wyniki przyrostowe.

Zobacz też