Udostępnij za pośrednictwem


Najlepsze rozwiązania w zakresie implementacji wzorca asynchronicznego opartego na zdarzeniach

Wzorzec asynchroniczny oparty na zdarzeniach zapewnia skuteczny sposób uwidaczniania asynchronicznego zachowania w klasach ze znanymi zdarzeniami i semantyki 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.

Zakoń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, a ukończenie nigdy nie występuje. Jednym wyjątkiem od tej reguły jest to, że sama operacja asynchroniczna została zaprojektowana tak, aby nigdy nie została ukończona.

Ukończono zdarzenie i eventArgs

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 pochodzącego AsyncCompletedEventArgs z klasy . 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ć EventArgs klasy dla zwracanych metod, które zwracają voidwartość . Zamiast tego użyj wystąpienia AsyncCompletedEventArgs klasy .

  • 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, a ukończenie nigdy nie występuje.

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

  • Jeśli wystąpił błąd podczas wykonywania zadania, wyniki nie powinny być dostępne. Error Jeśli właściwość nie nullma wartości , upewnij się, że uzyskiwanie dostępu do dowolnej właściwości w EventArgs strukturze 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 element TimeoutException do Error właściwości .

  • 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ń, włącz deweloperowi śledzenie każdego wywołania oddzielnie, definiując przeciążenie MethodNameAsync , które przyjmuje parametr stanu wartości obiektu lub identyfikator zadania o nazwie userSuppliedState. Ten parametr powinien zawsze być ostatnim parametrem w podpisie metody MethodNameAsync .

  • Jeśli klasa definiuje przeciążenie MethodNameAsync , które przyjmuje parametr stanu o wartości obiektu lub identyfikator zadania, pamiętaj o śledzeniu okresu istnienia operacji przy użyciu tego identyfikatora zadania i upewnij się, że podać go z powrotem 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ę MethodName Async 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 załóżmy, że deweloperzy mogą obsługiwać te wiele niedyskrywalnych wywołań zwrotnych

Uzyskiwanie dostępu do wyników

Raportowanie postępu

  • Obsługa raportowania postępu, jeśli to możliwe. Dzięki temu deweloperzy mogą zapewnić lepsze środowisko użytkownika aplikacji podczas korzystania z 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ę, że ProgressPercentage zawsze można je 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 serwery proxy usługi sieci Web XML nie uwidaczniają IsBusy właściwości, ponieważ obsługują wiele współbieżnych wywołań metod asynchronicznych.

  • Właściwość powinna zostać zwrócona IsBusytrue po wywołaniu metody MethodNameAsync i przed wywołaniu zdarzenia MethodNameCompleted . W przeciwnym razie powinna 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, obsługa anulowania. Dzięki temu deweloperzy mogą zapewnić lepsze środowisko użytkownika aplikacji podczas korzystania z klasy.

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

  • 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ę.

  • Upewnij się, że wywołania metody anulowania zawsze zwracają się pomyślnie i nigdy nie zgłaszają wyjątku. Ogólnie rzecz biorąc, klient jest powiadamiany o tym, czy operacja jest naprawdę anulowana w danym momencie, i nie jest powiadamiany o tym, czy wcześniej wystawione anulowanie zakończyło się pomyślnie. Jednak aplikacja będzie zawsze otrzymywać powiadomienia po pomyślnym anulowaniu, ponieważ aplikacja bierze udział w stanie 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 ustaw wartość AsyncCompletedEventArgs.Error właściwości na ten wyjątek.

Wątkowanie i konteksty

Prawidłowe działanie klasy ma kluczowe znaczenie, że programy obsługi zdarzeń klienta są wywoływane we właściwym wątku lub kontekście dla danego 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. Wywołania CreateOperation metody MethodNameAsync i klasa używa zwracanego AsyncOperation elementu w celu śledzenia okresu istnienia zadania asynchronicznego.

Aby zgłosić postęp, wyniki przyrostowe i ukończenie do klienta, wywołaj Post metody i OperationCompleted w pliku AsyncOperation. AsyncOperation odpowiada za wywołania marshalingu do programów obsługi zdarzeń klienta do odpowiedniego wątku lub kontekstu.

Uwaga

Te reguły można obejść, jeśli jawnie chcesz stosować zasady modelu aplikacji, ale nadal korzystać z innych zalet korzystania ze wzorca asynchronicznego opartego na zdarzeniach. Na przykład możesz chcieć, aby klasa działająca w formularzach Windows Forms mogła być bezpłatna wątek. 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 mogą być wywoływane poza kolejnością. Jeśli chcesz serializować wykonywanie wywołań, zaimplementuj Post i zainstaluj klasę System.Threading.SynchronizationContext .

Aby uzyskać więcej informacji na temat używania funkcji AsyncOperation i AsyncOperationManager włączania operacji asynchronicznych, 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).

Wytyczne

  • W idealnym przypadku każde wywołanie metody powinno być niezależne od innych. 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 za pomocą lock regionów, ponieważ różne wywołania dodają i usuń 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 deweloperów do rzutowania wartości pobranej z właściwości na obiekcie EventArgs nigdy nie jest akceptowalne.

  • Jeśli tworzysz klasę pochodzącą z Componentklasy , nie implementuj i zainstaluj własną SynchronizationContext klasę. Modele aplikacji, a nie składniki, kontrolują SynchronizationContext używane elementy.

  • W przypadku korzystania z wielowątków każdego rodzaju potencjalnie narażasz się na bardzo poważne i złożone błędy. Przed zaimplementowaniem dowolnego rozwiązania korzystającego z wielowątku zobacz Managed Threading Best Practices (Najlepsze rozwiązania dotyczące zarządzanych wątków).

Zobacz też