Więc coś mnie denerwuje w związku z nową obsługą asynchronizacji w C # 5:
Użytkownik naciska przycisk, który rozpoczyna operację asynchroniczną. Połączenie natychmiast wraca, a pompa komunikatów zaczyna działać ponownie - o to właśnie chodzi.
Aby użytkownik mógł ponownie nacisnąć przycisk - powodując ponowne wejście. Co jeśli jest to problem?
W pokazach, które widziałem, wyłączają przycisk przed await
połączeniem i włączają go ponownie później. Wydaje mi się, że jest to bardzo delikatne rozwiązanie w rzeczywistej aplikacji.
Czy powinniśmy kodować jakiś automat stanów, który określa, które kontrolki muszą być wyłączone dla danego zestawu uruchomionych operacji? Czy jest jakiś lepszy sposób?
Kusi mnie, aby po prostu pokazać modalne okno dialogowe na czas trwania operacji, ale to trochę przypomina używanie młota.
Czy ktoś ma jakieś jasne pomysły?
EDYTOWAĆ:
Myślę, że wyłączenie kontrolek, których nie należy używać podczas wykonywania operacji, jest delikatne, ponieważ myślę, że szybko się skomplikuje, gdy pojawi się okno z wieloma kontrolkami. Lubię uprościć sprawę, ponieważ zmniejsza ryzyko błędów, zarówno podczas początkowego kodowania, jak i późniejszej konserwacji.
Co się stanie, jeśli istnieje zbiór elementów sterujących, które należy wyłączyć dla określonej operacji? A jeśli wiele operacji działa jednocześnie?
źródło
Odpowiedzi:
Zacznijmy od zauważenia, że jest to już problem, nawet bez wsparcia asynchronicznego w języku. Wiele rzeczy może powodować, że wiadomości są usuwane z kolejki podczas obsługi zdarzenia. W tej sytuacji musisz już kodować obronnie; nowa funkcja języka czyni to bardziej oczywistym , czyli dobrocią.
Najczęstszym źródłem pętli komunikatów działającej podczas zdarzenia powodującego niechciane ponowne wejście jest
DoEvents
. Zaletąawait
w przeciwieństwie doDoEvents
jest to, żeawait
sprawia, że jest bardziej prawdopodobne, że nowe zadania nie idą do „zagłodzić” aktualnie uruchomionych zadań.DoEvents
pompuje pętlę komunikatów, a następnie synchronicznie wywołuje procedurę obsługi zdarzenia ponownie uczestnika, podczas gdyawait
zwykle kolejkuje nowe zadanie, aby mogło zostać uruchomione w pewnym momencie w przyszłości.Możesz zarządzać tą złożonością w taki sam sposób, jak każdą inną złożonością w języku OO: dzielisz mechanizmy na klasę i czynisz klasę odpowiedzialną za prawidłowe wdrożenie polityki . (Staraj się logicznie odróżniać mechanizmy od polityki; powinna istnieć możliwość zmiany polityki bez ingerowania w mechanizmy).
Jeśli masz formularz z wieloma kontrolkami, które działają w interesujący sposób , żyjesz w świecie złożoności własnego tworzenia . Musisz napisać kod, aby zarządzać tą złożonością; jeśli ci się to nie podoba, uprość formularz, aby nie był tak skomplikowany.
Użytkownicy tego nie znoszą.
źródło
Jak myślisz, dlaczego wyłączenie przycisku przed,
await
a następnie ponowne włączenie go po zakończeniu połączenia jest delikatne? Czy to dlatego, że obawiasz się, że przycisk nigdy nie zostanie ponownie włączony?Cóż, jeśli połączenie nie powróci, nie będzie można go ponownie nawiązać, więc wygląda na to, że takie zachowanie jest pożądane. Wskazuje to na błąd, który należy naprawić. Jeśli upłynie limit czasu połączenia asynchronicznego, nadal może to pozostawić aplikację w nieokreślonym stanie - ponownie taki, w którym włączenie przycisku może być niebezpieczne.
Jedyny raz może to się popsuć, jeśli istnieje kilka operacji, które wpływają na stan przycisku, ale myślę, że takie sytuacje powinny być bardzo rzadkie.
źródło
Nie jestem pewien, czy dotyczy to ciebie, ponieważ zwykle używam WPF, ale widzę, że odwołujesz się do
ViewModel
niego, więc może.Zamiast wyłączać przycisk, ustaw
IsLoading
flagę natrue
. Następnie dowolny element interfejsu użytkownika, który chcesz wyłączyć, można powiązać z flagą. Efektem końcowym jest to, że zadaniem interfejsu użytkownika jest uporządkowanie własnego stanu włączenia, a nie logiki biznesowej.Oprócz tego większość przycisków w WPF jest powiązana z
ICommand
, i zwykle ustawiam wartośćICommand.CanExecute
równą!IsLoading
, więc automatycznie zapobiega wykonywaniu polecenia, gdy IsLoading ma wartość true (wyłącza również dla mnie przycisk)źródło
Nie ma w tym nic kruchego i tego oczekiwałby użytkownik. Jeśli użytkownik musi zaczekać przed ponownym naciśnięciem przycisku, należy poczekać.
Widzę, jak pewne scenariusze sterowania mogą sprawić, że podejmowanie decyzji, które elementy sterujące mają zostać wyłączone / włączone w danym momencie, jest dość skomplikowane, ale czy zaskakujące jest to, że zarządzanie złożonym interfejsem użytkownika byłoby skomplikowane? Jeśli masz zbyt wiele przerażających efektów ubocznych z określonej kontroli, zawsze możesz po prostu wyłączyć całą formę i rzucić „ładowanie” gigantycznego koncertu na całość. Jeśli tak jest w przypadku każdej kontroli, twój interfejs użytkownika prawdopodobnie nie jest dobrze zaprojektowany (ścisłe połączenie, słabe grupowanie kontroli itp.).
źródło
Wyłączenie widgetu jest najmniej złożoną i podatną na błędy opcją. Wyłączasz go w module obsługi przed rozpoczęciem operacji asynchronicznej i włączasz ponownie na końcu operacji. Dlatego wyłączanie i włączanie dla każdej kontrolki są lokalne dla obsługi tej kontroli.
Możesz nawet utworzyć opakowanie, które przejmie przekazanie i kontrolę (lub więcej z nich), wyłączy kontrolę i asynchronicznie uruchomi coś, co wykona przekazanie i ponowne włączenie kontroli. Powinien zająć się wyjątkami (w końcu włączyć), więc nadal działa niezawodnie, jeśli operacja się nie powiedzie. Możesz to połączyć z dodaniem wiadomości na pasku stanu, aby użytkownik wiedział, na co czeka.
Możesz dodać logikę do liczenia wyłączeń, więc system działa dalej, jeśli masz dwie operacje, które powinny wyłączyć trzecią, ale nie wykluczają się nawzajem. Kontrolę wyłączasz dwa razy, więc zostanie ona ponownie włączona tylko wtedy, gdy ponownie ją włączysz. To powinno obejmować większość przypadków, jednocześnie utrzymując je oddzielnie, tak bardzo, jak to możliwe.
Z drugiej strony wszystko takie jak maszyna stanowa stanie się złożone. Z mojego doświadczenia wynika, że wszystkie maszyny stanowe, które widziałem, były trudne w utrzymaniu, a twoja maszyna stanowa musiałaby obejmować cały dialog, wiążąc wszystko ze wszystkim.
źródło
Chociaż Jan Hudec i Rachel wyrazili pokrewne pomysły, sugerowałbym użycie czegoś takiego jak architektura Model-View - wyrażenie stanu formy w obiekcie i powiązanie elementów formularza z tym stanem. Spowodowałoby to wyłączenie przycisku w sposób, który można skalować w przypadku bardziej złożonych scenariuszy.
źródło