Udostępnij za pośrednictwem


Omówienie wzorca asynchronicznego opartego na zdarzeniach

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 wielowątkowych składnik BackgroundWorker 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.

  • Interakcja z operacjami asynchronicznymi w toku za pomocą 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 MethodNameCompleted 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. Kiedy wywołasz metodę LoadAsync, aplikacja będzie nadal działać, podczas gdy pobieranie będzie odbywać się 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 CancelAsync przesyła żądanie zatrzymania oczekującego pobierania, a po anulowaniu zadania zgłaszane jest zdarzenie LoadCompleted.

Ostrzeżenie

Istnieje możliwość, że pobieranie zakończy się w momencie zgłoszenia CancelAsync żądania, 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ę MethodNameAsync i odpowiadające mu zdarzenie MethodNameCompleted . Bardziej złożone klasy mogą mieć kilka metod MethodNameAsync , 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 działają 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, zanim wywołanie zostanie zwrócone. 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 metod asynchronicznych

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 Twojemu kodowi wielokrotne wywołanie Method1Async(string param, object userState) bez konieczności oczekiwania na zakończenie trwających operacji asynchronicznych. Jeśli z drugiej strony spróbujesz wywołać Method1Async(string param) przed ukończeniem poprzedniego wywołania, metoda zgłasza 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ążenia wielokrotnych wywołań, twój kod będzie potrzebował śledzić obiekty userState (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 / Notatka

Należy zachować ostrożność, aby podać unikatową wartość dla userState w wywołaniach przeciążeń wykorzystywanych w wielokrotnych wywołaniach. 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. CancelAsync przyjmuje parametr userState, 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ć.

Otrzymywanie aktualizacji postępu 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. Właściwość ta mieści się w zakresie od 0 do 100 i może służyć do aktualizowania właściwości Value obiektu ProgressBar. Jeśli jest wiele operacji asynchronicznych w toku, możesz użyć właściwości ProgressChangedEventArgs.UserState, aby odróżnić, która operacja zgłasza postęp.

Niektóre klasy mogą zgłaszać wyniki przyrostowe w miarę postępu operacji asynchronicznych. Te wyniki będą przechowywane w klasie pochodzącej z ProgressChangedEventArgs i będą wyświetlane jako właściwości w klasie pochodnej. Do tych wyników można uzyskać dostęp w procedurze obsługi zdarzeń dla ProgressChanged zdarzenia, tak jak do właściwości ProgressPercentage. 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 także