Udostępnij za pomocą


Najlepsze rozwiązania dotyczące implementowania wzorca asynchronicznego opartego na zdarzeniach

Wzorzec asynchroniczny oparty na zdarzeniach zapewnia skuteczny sposób eksponowania asynchronicznego zachowania w klasach, z znajomymi zdarzeniami i semantyką delegatów. Aby zaimplementować wzorzec asynchroniczny oparty na zdarzeniach, należy przestrzegać określonych wymagań behawioralnych. W poniższych sekcjach opisano wymagania i wskazówki, które należy wziąć pod uwagę podczas implementowania klasy zgodnej ze wzorcem asynchronicznym opartym na zdarzeniach.

Aby zapoznać się z omówieniem, zobacz Implementowanie wzorca asynchronicznego opartego na zdarzeniach.

Wymagane gwarancje zachowania

W przypadku zaimplementowania wzorca asynchronicznego opartego na zdarzeniach należy podać szereg gwarancji, aby upewnić się, że klasa będzie działać prawidłowo, a klienci klasy mogą polegać na takim zachowaniu.

Ukończenie

Zawsze wywołaj procedurę obsługi zdarzeń MethodNameCompleted po pomyślnym zakończeniu, błędzie lub anulowaniu. Aplikacje nigdy nie powinny napotkać sytuacji, w której pozostają bezczynne i nigdy nie zostają ukończone. Jednym wyjątkiem od tej reguły jest to, że sama operacja asynchroniczna została zaprojektowana tak, aby nigdy nie została ukończona.

Zakończono zdarzenie i argumenty zdarzenia

Dla każdej oddzielnej metody MethodNameAsync zastosuj następujące wymagania projektowe:

  • Zdefiniuj zdarzenie MethodNameCompleted w tej samej klasie co metoda.

  • Zdefiniuj klasę EventArgs i towarzyszący delegat dla zdarzenia MethodNameCompleted, które pochodzą z klasy AsyncCompletedEventArgs. Domyślna nazwa klasy powinna mieć postać MethodNameCompletedEventArgs.

  • Upewnij się, że EventArgs klasa jest specyficzna dla zwracanych wartości metody MethodName . W przypadku korzystania z EventArgs klasy nigdy nie należy wymagać od deweloperów rzutowania wyniku.

    Poniższy przykład kodu pokazuje odpowiednio dobrą i złą implementację tego wymagania projektowego.

// Good design
private void Form1_MethodNameCompleted(object sender, xxxCompletedEventArgs e)
{
    DemoType result = e.Result;
}

// Bad design
private void Form1_MethodNameCompleted(object sender, MethodNameCompletedEventArgs e)
{
    DemoType result = (DemoType)(e.Result);
}
  • Nie należy definiować klasy EventArgs dla metod zwracających void. Zamiast tego użyj wystąpienia klasy AsyncCompletedEventArgs.

  • Upewnij się, że zawsze zgłaszasz zdarzenie MethodNameCompleted . To zdarzenie powinno zostać zgłoszone po pomyślnym zakończeniu, błędzie lub anulowaniu. Aplikacje nigdy nie powinny napotkać sytuacji, w której pozostają bezczynne i nigdy nie zostają ukończone.

  • Upewnij się, że przechwytujesz wszelkie wyjątki występujące w operacji asynchronicznej i przypisz przechwycony wyjątek do właściwości Error.

  • Jeśli wystąpił błąd podczas wykonywania zadania, wyniki nie powinny być dostępne. Error właściwość nie jest null, upewnij się, że uzyskiwanie dostępu do jakiejkolwiek właściwości w strukturze EventArgs zgłasza wyjątek. RaiseExceptionIfNecessary Użyj metody , aby przeprowadzić tę weryfikację.

  • Modelowanie limitu czasu jako błędu. Gdy wystąpi przekroczenie limitu czasu, zgłoś zdarzenie MethodNameCompleted i przypisz TimeoutException do właściwości Error.

  • Jeśli klasa obsługuje wiele współbieżnych wywołań, upewnij się, że zdarzenie MethodNameCompleted zawiera odpowiedni userSuppliedState obiekt.

  • Upewnij się, że zdarzenie MethodNameCompleted jest wywoływane w odpowiednim wątku i w odpowiednim czasie w cyklu życia aplikacji. Aby uzyskać więcej informacji, zobacz sekcję Wątkowanie i konteksty.

Jednoczesne wykonywanie operacji

  • Jeśli klasa obsługuje wiele współbieżnych wywołań, umożliw deweloperowi odrębne śledzenie każdego wywołania, definiując przeciążenie MethodNameAsync, które przyjmuje parametr stanu o wartości obiektu lub identyfikator zadania o nazwie userSuppliedState. Ten parametr powinien zawsze być ostatnim parametrem w podpisie metody MethodNameAsync .

  • Jeśli Twoja klasa definiuje przeciążenie MethodNameAsync, które przyjmuje parametr stanu jako obiekt lub identyfikator zadania, pamiętaj, aby śledzić trwałość operacji związanej z tym identyfikatorem zadania oraz upewnij się, że przekażesz go ponownie do procedury obsługi ukończenia. Dostępne są klasy pomocnicze, które ułatwiają pomoc. Aby uzyskać więcej informacji na temat zarządzania współbieżnością, zobacz How to: Implement a Component That Supports the Event-based Asynchronous Pattern (Instrukcje: implementowanie składnika obsługującego wzorzec asynchroniczny oparty na zdarzeniach).

  • Jeśli klasa definiuje metodę MethodNameAsync bez parametru stanu i nie obsługuje wielu współbieżnych wywołań, upewnij się, że każda próba wywołania MethodNameAsync przed ukończeniem poprzedniej wywołania MethodNameAsync zgłasza błąd InvalidOperationException.

  • Ogólnie rzecz biorąc, nie zgłaszaj wyjątku, jeśli metoda MethodNameAsync bez parametru userSuppliedState jest wywoływana wiele razy, aby istnieje wiele zaległych operacji. Możesz zgłosić wyjątek, gdy klasa jawnie nie może obsłużyć tej sytuacji, ale zakładajmy, że deweloperzy mogą obsługiwać te liczne nierozróżnialne wywołania zwrotne.

Uzyskiwanie dostępu do wyników

Raportowanie postępu

  • Obsługa raportowania postępu, jeśli to możliwe. To umożliwia deweloperom zapewnienie lepszego doświadczenia użytkownika aplikacji przy użyciu klasy.

  • W przypadku zaimplementowania zdarzenia ProgressChanged lub MethodNameProgressChanged upewnij się, że nie ma żadnych takich zdarzeń zgłoszonych dla określonej operacji asynchronicznej po wywołaniu zdarzenia MethodNameCompleted tej operacji.

  • Jeśli standard ProgressChangedEventArgs jest wypełniany, upewnij się, aby ProgressPercentage zawsze można było interpretować jako wartość procentową. Wartość procentowa nie musi być dokładna, ale powinna reprezentować wartość procentową. Jeśli metryka raportowania postępu musi być czymś innym niż wartość procentowa, utwórz klasę z ProgressChangedEventArgs klasy i pozostaw ProgressPercentage wartość 0. Unikaj używania metryki raportowania innej niż wartość procentowa.

  • Upewnij się, że ProgressChanged zdarzenie jest wywoływane w odpowiednim wątku i w odpowiednim czasie w cyklu życia aplikacji. Aby uzyskać więcej informacji, zobacz sekcję Wątkowanie i konteksty.

Implementacja IsBusy

  • Nie uwidaczniaj IsBusy właściwości, jeśli klasa obsługuje wiele współbieżnych wywołań. Na przykład pełnomocnicy usług sieci Web XML nie ujawniają właściwości IsBusy, ponieważ umożliwiają wiele równoczesnych wywołań metod asynchronicznych.

  • Właściwość powinna zwracać IsBusytrue po wywołaniu metody MethodNameAsync i przed wywołaniem zdarzenia MethodNameCompleted. W przeciwnym razie powinien zwrócić wartość false. Składniki BackgroundWorker i WebClient to przykłady klas, które uwidaczniają IsBusy właściwość.

Anulowanie

  • Jeśli to możliwe, wspieraj anulowanie. To umożliwia deweloperom zapewnienie lepszego doświadczenia użytkownika aplikacji przy użyciu klasy.

  • W przypadku anulowania ustaw flagę Cancelled w obiekcie AsyncCompletedEventArgs.

  • Upewnij się, że każda próba uzyskania dostępu do wyniku zgłasza informację InvalidOperationException o anulowaniu operacji. AsyncCompletedEventArgs.RaiseExceptionIfNecessary Użyj metody , aby przeprowadzić tę weryfikację.

  • Ustaw, aby wywołania metody anulowania zawsze kończyły się pomyślnie i nigdy nie zgłaszały wyjątku. Ogólnie rzecz biorąc, klient nie jest powiadamiany, czy operacja jest w danym momencie rzeczywiście anulowalna, ani czy wcześniej wystawione anulowanie odniosło sukces. Jednak aplikacja będzie zawsze otrzymywać powiadomienie po pomyślnym anulowaniu, ponieważ aplikacja bierze udział w procesie ukończenia.

  • Zgłoś zdarzenie MethodNameCompleted po anulowaniu operacji.

Błędy i wyjątki

  • Przechwyć wszelkie wyjątki występujące w operacji asynchronicznej i przypisz wartość AsyncCompletedEventArgs.Error właściwości temu wyjątkowi.

Wątkowanie i konteksty

Dla prawidłowego działania Twojej klasy kluczowe jest, aby procedury obsługi zdarzeń klienta były wywoływane we właściwym wątku lub kontekście dla określonego modelu aplikacji, w tym aplikacji ASP.NET i Windows Forms. Dostępne są dwie ważne klasy pomocnicze, aby upewnić się, że klasa asynchroniczna działa prawidłowo w dowolnym modelu aplikacji: AsyncOperation i AsyncOperationManager.

AsyncOperationManager udostępnia jedną metodę , CreateOperationktóra zwraca wartość AsyncOperation. Metoda MethodNameAsync wywołuje CreateOperation, a klasa używa zwróconego AsyncOperation do śledzenia okresu istnienia zadania asynchronicznego.

Aby zgłosić postęp, wyniki przyrostowe i ukończenie do klienta, wywołaj metody Post i OperationCompleted na obiekcie AsyncOperation. AsyncOperation odpowiada za obsługę wywołań procedur obsługi zdarzeń klienta, kierując je do odpowiedniego wątku lub kontekstu.

Uwaga / Notatka

Te reguły można obejść, jeśli jawnie chcesz sprzeciwić się zasadom modelu aplikacji, ale nadal korzystać z innych zalet stosowania wzorca asynchronicznego opartego na zdarzeniach. Na przykład możesz chcieć, aby klasa działająca w Windows Forms była wolnowątkowa. Możesz utworzyć bezpłatną klasę wątkową, o ile deweloperzy rozumieją dorozumiane ograniczenia. Aplikacje konsolowe nie synchronizują wykonywania wywołań Post . Może to spowodować, że ProgressChanged zdarzenia są wywoływane poza kolejnością. Aby mieć sekwencyjne wykonywanie wywołań Post, zaimplementuj i zainstaluj klasę System.Threading.SynchronizationContext.

Aby uzyskać więcej informacji na temat używania AsyncOperation i AsyncOperationManager do włączania operacji asynchronicznych, zobacz „Instrukcje: implementowanie składnika obsługującego asynchroniczny wzorzec oparty na zdarzeniach”.

Wytyczne

  • W idealnym przypadku każde wywołanie metody powinno być niezależne od pozostałych. Należy unikać sprzęgania wywołań z udostępnionymi zasobami. Jeśli zasoby mają być współużytkowane przez wywołania, należy zapewnić odpowiedni mechanizm synchronizacji w implementacji.

  • Nie zaleca się projektowania, które wymagają od klienta implementacji synchronizacji. Na przykład można mieć metodę asynchroniczną, która odbiera globalny obiekt statyczny jako parametr; wiele współbieżnych wywołań takiej metody może spowodować uszkodzenie danych lub zakleszczenia.

  • Jeśli zaimplementujesz metodę z przeciążeniem wielu wywołań (userState w podpisie), klasa będzie musiała zarządzać kolekcją stanów użytkownika lub identyfikatorami zadań oraz odpowiadającymi im oczekującymi operacjami. Ta kolekcja powinna być chroniona przy użyciu lock regionów, ponieważ różne wywołania dodają i usuwają userState obiekty w kolekcji.

  • Rozważ ponowne użycie CompletedEventArgs klas tam, gdzie jest to możliwe i odpowiednie. W takim przypadku nazewnictwo nie jest zgodne z nazwą metody, ponieważ dany delegat i EventArgs typ nie są powiązane z jedną metodą. Jednak zmuszanie programistów do rzutowania wartości uzyskanej z właściwości na obiekcie EventArgs nigdy nie jest akceptowalne.

  • Jeśli tworzysz klasę pochodną z Component klasy, nie implementuj i nie instaluj swojej własnej klasy SynchronizationContext. Modele aplikacji, a nie składniki, kontrolują używane SynchronizationContext.

  • Przy użyciu programowania wielowątkowego wszelkiego rodzaju potencjalnie narażasz się na bardzo poważne i złożone błędy. Przed zaimplementowaniem dowolnego rozwiązania korzystającego z wielowątkowości zobacz Managed Threading Best Practices (Najlepsze praktyki dotyczące zarządzania wątkami).

Zobacz także