Udostępnij za pośrednictwem


Implementowanie 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).

Dla prostych operacji asynchronicznych odpowiedni może być komponent BackgroundWorker. 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

  • Opcjonalna obsługa anulowania

  • Opcjonalne wsparcie dla właściwości IsBusy

  • Opcjonalnie zapewnij obsługę raportowania postępu

  • Umożliw obsługę zwracania wyników przyrostowych opcjonalnie

  • 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ą, aby obiekty WaitHandle i IAsyncResult były dostępne dla operacji asynchronicznych, co oznacza, że sondowanie oraz WaitAll lub WaitAny będą musiały być przygotowane 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 wywołują 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 oraz wynikach przyrostowych lub zmianach 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 metody MethodNameAsync, które jest identyczne z MethodNameAsync, ale z dodatkowym parametrem obiektowym o nazwie userState. Zrób to, jeśli chcesz zarządzać wieloma współbieżnymi wywołaniami metody, w takim przypadku wartość userState zostanie zwrócona do wszystkich obsług 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 po prostu użyć AsyncCompletedEventArgs.

      Uwaga / Notatka

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

Opcjonalna 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 wsparcia przy anulowaniu należy osiągnąć dwa punkty decyzyjne.

  • Czy Twoja klasa, w tym zmiany planowane w przyszłości, ma tylko jedną operację asynchroniczną, która umożliwia anulowanie?
  • Czy operacje asynchroniczne, które obsługują anulowanie, obsługują wiele oczekujących operacji? Oznacza to, czy metoda MethodNameAsync przyjmuje parametr userState i czy zezwala na wiele wywołań przed oczekiwaniem na zakończenie któregokolwiek z nich?

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 asynchroniczna 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 asynchroniczna 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 nazwaniu wersji z jedną operacją asynchroniczną methodNameAsyncCancel opiera się na możliwościach łatwiejszego odnalezienia metody w środowisku projektowym, takim jak IntelliSense w Visual Studio. Grupuje to powiązanych członków i odróżnia ich od innych członków, którzy nie mają nic wspólnego z funkcjonalnością asynchroniczną. Jeśli spodziewasz się, że w kolejnych wersjach mogą zostać dodane dodatkowe operacje asynchroniczne, lepiej zdefiniować CancelAsync.

Nie należy definiować wielu metod z powyższej tabeli w tej samej klasie. To nie będzie miało sensu albo sprawi, że interfejs klasy będzie przeładowany proliferacją 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 MethodNameCompleted 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).

Opcjonalne wsparcie dla właściwości 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 jej trwania. 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 zgodny z wyborem dokonanym dla metody odwołania, jak opisano w sekcji Opcjonalne wsparcie anulowania.

To zdarzenie powinno używać sygnatury delegata ProgressChangedEventHandler i klasy ProgressChangedEventArgs. Alternatywnie, jeśli można dostarczyć bardziej specyficzny dla domeny wskaźnik postępu (na przykład odczytane bajty i całkowitą liczbę bajtów dla operacji pobierania), należy zdefiniować ProgressChangedEventArgs jako pochodną klasy.

Należy pamiętać, że dla klasy istnieje tylko jedno zdarzenie ProgressChangedMethodNameProgressChanged, niezależnie od liczby obsługiwanych przez nią metod asynchronicznych. Oczekuje się, że klienci będą używać obiektu przekazanego userState do 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 wspiera postęp i każda 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 MethodNameProgressChanged dla każdej metody MethodNameAsync.

Przestrzegaj semantyki raportowania postępu opisanej w Najlepsze Praktyki dotyczące implementowania asynchronicznego wzorca opartego na zdarzeniach.

Umożliw obsługę zwracania wyników przyrostowych opcjonalnie

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 operacji jednorazowej

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 wyłącznie do klasy pojedynczej operacji asynchronicznej, ponieważ nie ma problemu, by to samo zdarzenie mogło zwracać wyniki przyrostowe dla "wszystkich operacji", jak czyni to zdarzenie MethodNameProgressChanged.

Klasa wielozadaniowa z jednorodnymi przyrostowymi wynikami

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 zdarzenie ProgressChanged zamiast zdarzenia MethodNameProgressChanged, ponieważ dotyczy to 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 z odpowiednimi parametrami dla każdej metody asynchronicznej, aby obsługiwać stopniowe wyniki danych tej metody.

Wywołaj tę procedurę obsługi zdarzeń w odpowiednim wątku zgodnie z opisem w najlepszych praktykach implementowania wzorca asynchronicznego opartego na zdarzeniach).

Obsługa parametrów out i ref w metodach

Chociaż stosowanie out i ref jest ogólnie odradzane w .NET, oto zasady, których należy przestrzegać, gdy są obecne:

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

  • out parametry 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).

  • ref parametry MethodName powinny pojawić się jako część MethodNameAsync i jako część MethodNameCompletedEventArgs z tą samą nazwą jak jego odpowiednik parametru w MethodName (chyba że istnieje bardziej odpowiednia nazwa).

Na przykład, biorąc pod uwagę:

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 także