Implementacja wzorca asynchronicznego opartego na zdarzeniach

Jeśli piszesz klasę z niektórymi operacjami, które mogą powodować zauważalne opóźnienia, rozważ nadanie jej funkcji asynchronicznej przez zaimplementowanie asynchronicznego wzorca opartego na zdarzeniach.

Wzorzec asynchroniczny oparty na zdarzeniach zapewnia ustandaryzowany sposób tworzenia pakietu klasy, która ma funkcje asynchroniczne. Jeśli zaimplementowano przy użyciu klas pomocnika, takich jak AsyncOperationManager, klasa będzie działać poprawnie w dowolnym modelu aplikacji, w tym w ASP.NET, aplikacjach konsolowych i aplikacjach Windows Forms.

Przykład implementujący asynchroniczny wzorzec oparty na zdarzeniach, zobacz How to: Implement a Component That Supports the Event-based Asynchronous Pattern (Instrukcje: implementowanie składnika obsługującego asynchroniczny wzorzec oparty na zdarzeniach).

W przypadku prostych operacji asynchronicznych można znaleźć BackgroundWorker odpowiedni składnik. Aby uzyskać więcej informacji na temat BackgroundWorkerprogramu , zobacz How to: Run an Operation in the Background (Instrukcje: uruchamianie operacji w tle).

Poniższa lista zawiera opis funkcji asynchronicznego wzorca opartego na zdarzeniach omówionego w tym temacie.

  • Możliwości implementacji wzorca asynchronicznego opartego na zdarzeniach

  • Nazewnictwo metod asynchronicznych

  • Opcjonalnie obsługa anulowania

  • Opcjonalnie obsługują właściwość IsBusy

  • Opcjonalnie zapewnij obsługę raportowania postępu

  • Opcjonalnie podaj obsługę zwracania wyników przyrostowych

  • Obsługa parametrów out i ref w metodach

Możliwości implementacji wzorca asynchronicznego opartego na zdarzeniach

Rozważ zaimplementowanie wzorca asynchronicznego opartego na zdarzeniach, gdy:

  • Klienci klasy nie potrzebują WaitHandle obiektów i IAsyncResult są dostępne dla operacji asynchronicznych, co oznacza, że sondowanie i WaitAll lub WaitAny konieczne będzie skompilowanie przez klienta.

  • Chcesz, aby operacje asynchroniczne były zarządzane przez klienta za pomocą znanego modelu zdarzenia/delegata.

Każda operacja jest kandydatem do implementacji asynchronicznej, ale należy wziąć pod uwagę te, które powinny powodować długie opóźnienia. Szczególnie odpowiednie są operacje, w których klienci nazywają metodę i są powiadamiani po zakończeniu, bez konieczności dalszej interwencji. Odpowiednie są również operacje, które są uruchamiane w sposób ciągły, okresowo powiadamiając klientów o postępie, wyniki przyrostowe lub zmiany stanu.

Aby uzyskać więcej informacji na temat określania, kiedy należy obsługiwać wzorzec asynchroniczny oparty na zdarzeniach, zobacz Wybieranie, kiedy należy zaimplementować wzorzec asynchroniczny oparty na zdarzeniach.

Nazewnictwo metod asynchronicznych

Dla każdej synchronicznej metody MethodName , dla której chcesz podać asynchroniczny odpowiednik:

Zdefiniuj metodę MethodNameAsync , która:

  • Zwraca wartość void.

  • Przyjmuje te same parametry co metoda MethodName .

  • Akceptuje wiele wywołań.

Opcjonalnie zdefiniuj przeciążenie MethodName Async, identyczne z metodą MethodNameAsync, ale z dodatkowym parametrem o nazwie userState. Zrób to, jeśli chcesz zarządzać wieloma współbieżnych wywołań metody, w takim przypadku userState wartość zostanie dostarczona z powrotem do wszystkich procedur obsługi zdarzeń w celu odróżnienia wywołań metody. Możesz również to zrobić po prostu jako miejsce do przechowywania stanu użytkownika na potrzeby późniejszego pobierania.

Dla każdej oddzielnej sygnatury metody MethodNameAsync :

  1. Zdefiniuj następujące zdarzenie w tej samej klasie co metoda:

    Public Event MethodNameCompleted As MethodNameCompletedEventHandler
    
    public event MethodNameCompletedEventHandler MethodNameCompleted;
    
  2. Zdefiniuj następujący delegat i AsyncCompletedEventArgs. Prawdopodobnie zostaną one zdefiniowane poza samą klasą, ale w tej samej przestrzeni nazw.

    Public Delegate Sub MethodNameCompletedEventHandler( _
        ByVal sender As Object, _
        ByVal e As MethodNameCompletedEventArgs)
    
    Public Class MethodNameCompletedEventArgs
        Inherits System.ComponentModel.AsyncCompletedEventArgs
    Public ReadOnly Property Result() As MyReturnType
    End Property
    
    public delegate void MethodNameCompletedEventHandler(object sender,
        MethodNameCompletedEventArgs e);
    
    public class MethodNameCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs
    {
        public MyReturnType Result { get; }
    }
    
    • Upewnij się, że klasa MethodNameCompletedEventArgs uwidacznia jego składowe jako właściwości tylko do odczytu, a nie pola, ponieważ pola uniemożliwiają powiązanie danych.

    • Nie należy definiować żadnych AsyncCompletedEventArgsklas pochodnych dla metod, które nie generują wyników. Wystarczy użyć wystąpienia AsyncCompletedEventArgs samego siebie.

      Uwaga

      Jest to całkowicie akceptowalne, jeśli jest to możliwe i odpowiednie, aby ponownie używać delegatów i AsyncCompletedEventArgs typów. W takim przypadku nazewnictwo nie będzie zgodne z nazwą metody, ponieważ dany delegat i AsyncCompletedEventArgs nie będzie powiązany z jedną metodą.

Opcjonalnie obsługa anulowania

Jeśli klasa będzie obsługiwać anulowanie operacji asynchronicznych, anulowanie powinno zostać ujawnione klientowi zgodnie z poniższym opisem. Przed zdefiniowaniem pomocy technicznej anulowania należy uzyskać dwa punkty decyzyjne:

  • Czy klasa, w tym przyszłe przewidywane dodatki, ma tylko jedną operację asynchroniczną, która obsługuje anulowanie?
  • Czy operacje asynchroniczne, które obsługują anulowanie, obsługują wiele oczekujących operacji? Oznacza to, czy metoda MethodNameAsync bierze userState parametr i czy zezwala na wiele wywołań przed oczekiwaniem na zakończenie?

Skorzystaj z odpowiedzi na te dwa pytania w poniższej tabeli, aby określić, jaki powinien być podpis metody anulowania.

Visual Basic

Obsługa wielu równoczesnych operacji Tylko jedna operacja w danym momencie
Jedna operacja asynchronizuj w całej klasie Sub MethodNameAsyncCancel(ByVal userState As Object) Sub MethodNameAsyncCancel()
Wiele operacji asynchronicznych w klasie Sub CancelAsync(ByVal userState As Object) Sub CancelAsync()

C#

Obsługa wielu równoczesnych operacji Tylko jedna operacja w danym momencie
Jedna operacja asynchronizuj w całej klasie void MethodNameAsyncCancel(object userState); void MethodNameAsyncCancel();
Wiele operacji asynchronicznych w klasie void CancelAsync(object userState); void CancelAsync();

Jeśli zdefiniujesz metodę CancelAsync(object userState) , klienci muszą zachować ostrożność podczas wybierania ich wartości stanu, aby umożliwić im rozróżnienie między wszystkimi metodami asynchronicznymi wywoływanymi w obiekcie, a nie tylko między wszystkimi wywołaniami pojedynczej metody asynchronicznej.

Decyzja o nazwie methodNameAsyncCancel o jedną operację asynchronicznego polega na tym, że można łatwiej odnaleźć metodę w środowisku projektowym, na przykład IntelliSense programu Visual Studio. Spowoduje to zgrupowanie powiązanych członków i odróżnienie ich od innych elementów członkowskich, które nie mają nic wspólnego z funkcją asynchroniczną. Jeśli spodziewasz się, że w kolejnych wersjach mogą istnieć dodatkowe operacje asynchroniczne, lepiej jest zdefiniować CancelAsyncelement .

Nie należy definiować wielu metod z powyższej tabeli w tej samej klasie. To nie będzie miało sensu lub będzie zaśmiecać interfejs klasy z proliferacji metod.

Te metody zazwyczaj będą zwracane natychmiast, a operacja może lub nie może zostać anulowana. W procedurze obsługi zdarzeń dla zdarzenia MethodName Completed obiekt MethodNameCompletedEventArgs zawiera Cancelled pole, którego klienci mogą użyć do określenia, czy nastąpiło anulowanie.

Przestrzegaj semantyki anulowania opisanej w artykule Best Practices for Implement the Event-based Asynchronous Pattern (Najlepsze rozwiązania dotyczące implementowania wzorca asynchronicznego opartego na zdarzeniach).

Opcjonalnie obsługują właściwość IsBusy

Jeśli klasa nie obsługuje wielu współbieżnych wywołań, rozważ ujawnienie IsBusy właściwości. Dzięki temu deweloperzy mogą określić, czy metoda MethodNameAsync jest uruchomiona bez przechwytywania wyjątku od metody MethodNameAsync.

Przestrzegaj semantyki opisanej IsBusy w artykule Best Practices for Implement the Event-based Asynchronous Pattern (Najlepsze rozwiązania dotyczące implementowania wzorca asynchronicznego opartego na zdarzeniach).

Opcjonalnie zapewnij obsługę raportowania postępu

Często pożądane jest, aby operacja asynchroniczna zgłaszała postęp podczas jego operacji. Wzorzec asynchroniczny oparty na zdarzeniach zawiera wskazówki dotyczące tego działania.

  • Opcjonalnie zdefiniuj zdarzenie, które ma być wywoływane przez operację asynchroniczną i wywoływane w odpowiednim wątku. Obiekt ProgressChangedEventArgs zawiera wskaźnik postępu o wartości całkowitej, który powinien należeć do przedziału od 0 do 100.

  • Nadaj temu zdarzeniu nazwę w następujący sposób:

    • ProgressChanged jeśli klasa ma wiele operacji asynchronicznych (lub oczekuje się, że wzrośnie, aby uwzględnić wiele operacji asynchronicznych w przyszłych wersjach);

    • MethodNameProgressChanged , jeśli klasa ma jedną operację asynchroniczną.

    Ten wybór nazewnictwa jest równoległy, który został wykonany dla metody anulowania, zgodnie z opisem w sekcji Opcjonalne anulowanie obsługi.

To zdarzenie powinno używać sygnatury delegata ProgressChangedEventHandler i ProgressChangedEventArgs klasy . Alternatywnie, jeśli można podać bardziej specyficzny dla domeny wskaźnik postępu (na przykład bajty odczytu i sumy bajtów dla operacji pobierania), należy zdefiniować klasę ProgressChangedEventArgspochodną klasy .

Należy pamiętać, że dla klasy istnieje tylko jedno ProgressChanged zdarzenie Lub MethodNameProgressChanged , niezależnie od liczby obsługiwanych przez nią metod asynchronicznych. Oczekuje się, że klienci będą używać obiektu przekazanego userStatedo metod MethodNameAsync w celu odróżnienia między aktualizacjami postępu na wielu współbieżnych operacjach.

Mogą wystąpić sytuacje, w których wiele operacji obsługuje postęp, a każdy zwraca inny wskaźnik postępu. W takim przypadku pojedyncze ProgressChanged zdarzenie nie jest odpowiednie i można rozważyć obsługę wielu ProgressChanged zdarzeń. W tym przypadku należy użyć wzorca nazewnictwa metody MethodNameProgressChanged dla każdej metody MethodNameAsync.

Przestrzegaj semantyki raportowania postępu opisane najlepsze rozwiązania dotyczące implementowania asynchronicznego wzorca opartego na zdarzeniach.

Opcjonalnie podaj obsługę zwracania wyników przyrostowych

Czasami operacja asynchroniczna może zwracać wyniki przyrostowe przed zakończeniem. Istnieje wiele opcji, których można użyć do obsługi tego scenariusza. Poniżej przedstawiono kilka przykładów.

Klasa pojedynczej operacji

Jeśli klasa obsługuje tylko jedną operację asynchroniczną, a ta operacja może zwrócić wyniki przyrostowe, wówczas:

  • ProgressChangedEventArgs Rozszerz typ tak, aby przenosił dane wynikowe przyrostowe, i zdefiniuj zdarzenie MethodNameProgressChanged przy użyciu tych rozszerzonych danych.

  • Zgłoś to zdarzenie MethodNameProgressChanged , gdy istnieje wynik przyrostowy do raportowania.

To rozwiązanie ma zastosowanie w szczególności do pojedynczej klasy operacji asynchronicznej, ponieważ nie ma problemu z tym samym zdarzeniem, które występuje w celu zwrócenia wyników przyrostowych dla "wszystkich operacji", jak to robi zdarzenie MethodNameProgressChanged .

Klasa wielokrotnej operacji z homogenicznymi wynikami przyrostowymi

W takim przypadku klasa obsługuje wiele metod asynchronicznych, z których każda może zwracać wyniki przyrostowe, a wszystkie te przyrostowe wyniki mają ten sam typ danych.

Postępuj zgodnie z modelem opisanym powyżej dla klas pojedynczej operacji, ponieważ ta sama EventArgs struktura będzie działać dla wszystkich wyników przyrostowych. Zdefiniuj ProgressChanged zdarzenie zamiast zdarzenia MethodNameProgressChanged , ponieważ dotyczy wielu metod asynchronicznych.

Klasa wielokrotnej operacji z heterogenicznymi wynikami przyrostowymi

Jeśli klasa obsługuje wiele metod asynchronicznych, każdy zwraca inny typ danych, należy wykonać następujące czynności:

  • Oddziel raportowanie wyników przyrostowych od raportowania postępu.

  • Zdefiniuj oddzielne zdarzenie MethodNameProgressChanged odpowiednie EventArgs dla każdej metody asynchronicznej w celu obsługi przyrostowych danych wynikowych tej metody.

Wywołaj tę procedurę obsługi zdarzeń w odpowiednim wątku zgodnie z opisem w artykule Best Practices for Implement the Event-based Asynchronous Pattern (Najlepsze rozwiązania dotyczące implementowania wzorca asynchronicznego opartego na zdarzeniach).

Obsługa parametrów out i ref w metodach

Mimo że korzystanie z platformy out i ref jest ogólnie zniechęcane do korzystania z platformy .NET, poniżej przedstawiono reguły, które należy przestrzegać, gdy są obecne:

Biorąc pod uwagę synchroniczną metodę MethodName:

  • outparametry methodName nie powinny być częścią metody MethodNameAsync. Zamiast tego powinny one być częścią MethodNameCompletedEventArgs o takiej samej nazwie jak jej odpowiednik parametru w MethodName (chyba że istnieje bardziej odpowiednia nazwa).

  • refparametry metody MethodName powinny być wyświetlane jako część metody MethodName Async, a w ramach metody MethodNameCompletedEventArgs o takiej samej nazwie jak jej odpowiednik parametru w MethodName (chyba że istnieje bardziej odpowiednia nazwa).

Na przykład podane:

Public Function MethodName(ByVal arg1 As String, ByRef arg2 As String, ByRef arg3 As String) As Integer
public int MethodName(string arg1, ref string arg2, out string arg3);

Metoda asynchroniczna i jej AsyncCompletedEventArgs klasa będą wyglądać następująco:

Public Sub MethodNameAsync(ByVal arg1 As String, ByVal arg2 As String)

Public Class MethodNameCompletedEventArgs
    Inherits System.ComponentModel.AsyncCompletedEventArgs
    Public ReadOnly Property Result() As Integer
    End Property
    Public ReadOnly Property Arg2() As String
    End Property
    Public ReadOnly Property Arg3() As String
    End Property
End Class
public void MethodNameAsync(string arg1, string arg2);

public class MethodNameCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs
{
    public int Result { get; };
    public string Arg2 { get; };
    public string Arg3 { get; };
}

Zobacz też