Rozważmy ten kod, który działa na wątku UI:Jak lepiej obsługiwać przy użyciu elementów sterujących rozmieszczonych asynchronicznie/czekają
dividends = await Database.GetDividends();
if (IsDisposed)
return;
//Do expensive UI work here
earnings = await Database.GetEarnings();
if (IsDisposed)
return;
//Do expensive UI work here
//etc...
pamiętać, że za każdym razem kiedy await
ja również sprawdzić IsDisposed
. Jest to konieczne, ponieważ mówię I await
o długim działaniu Task
. Tymczasem użytkownik zamyka formularz przed jego zakończeniem. Task
zakończy i uruchomi kontynuację, która będzie próbować uzyskać dostęp do kontroli w umieszczonej formie. Wystąpił wyjątek.
Czy istnieje lepszy sposób na obsłużenie tego lub uproszczenie tego wzorca? Używam await
swobodnie w kodzie UI i jest obrzydliwe, aby sprawdzić za każdym razem IsDisposed
i podatny na błędy, jeśli zapomnę.
EDIT:
Istnieje kilka proponowane rozwiązania, które nie pasowały, ponieważ zmieniają funkcjonalności.
- Prevent forma zamknięcia aż zadań tle komplecie
Będzie to udaremnić użytkowników. Ponadto nadal pozwala na potencjalnie kosztowne działanie GUI, które jest stratą czasu, szkodzi wydajności i nie jest już istotne. W przypadku, w którym prawie zawsze pracuję w tle, może to zapobiec zamknięciu formularza przez bardzo długi czas.
- Ukryj formularz i zamknąć go raz wszystkie zadania kompletne
Ma wszystkie problemy zapobiegania ścisłej formie poza nie udaremnić użytkowników. Kontynuacja kontynuacji kosztownej pracy GUI. Zwiększa to również złożoność śledzenia, gdy wszystkie zadania kończą, a następnie zamykają formularz, jeśli są ukryte.
- Użyj
CancellationTokenSource
, aby anulować wszystkie zadania, gdy formularz jest zamknięcie
ten nawet nie rozwiązanie problemu. W rzeczywistości już to robię (nie ma sensu marnować zasobów w tle). To nie jest rozwiązanie, ponieważ nadal muszę sprawdzić IsDisposed
ze względu na niejawny stan wyścigu. Poniższy kod pokazuje stan wyścigu.
Warunki wyścigu występują, gdy zadanie zostało już zakończone, formularz został zamknięty, a kontynuacja nadal trwa. Zobaczysz, że czasami pojawia się InvalidOperationException
. Anulowanie zadania jest dobrą praktyką, oczywiście, ale nie zmniejsza mnie to, że muszę sprawdzić IsDisposed
.
WYJAŚNIENIE
Oryginalny przykład kodu jest dokładnie co chcę pod względem funkcjonalności. To po prostu brzydki wzorzec i "czekanie na pracę w tle, a następnie aktualizowanie GUI" jest dość powszechnym przypadkiem użycia. Z technicznego punktu widzenia po prostu chcę, aby kontynuacja nie działała w ogóle, jeśli formularz zostanie usunięty.Przykładowy kod właśnie to robi, ale nie elegancko i jest podatny na błędy (jeśli zapomnę sprawdzić IsDisposed
na każdym await
wprowadzam błąd). Idealnie chcę napisać wrapper, metodę rozszerzenia itp., Która mogłaby zamknąć ten podstawowy projekt. Ale nie mogę wymyślić, jak to zrobić.
Ponadto, myślę, że muszę stwierdzić, że wydajność jest kwestią najwyższej jakości. Wyrzucenie wyjątku jest na przykład bardzo kosztowne z powodów, dla których nie będę mógł wejść. Dlatego też nie chcę po prostu próbować złapać ObjectDisposedException
za każdym razem, gdy robię await
. Jeszcze brzydszy kod, a także szkodzi wydajności. Wygląda na to, że po prostu sprawdzanie za każdym razem jest najlepszym rozwiązaniem, ale żałuję, że nie było lepszego sposobu.
EDIT # 2
dotyczących wydajności - tak to wszystko jest względne. Rozumiem, że ogromna większość programistów nie dba o koszt rzucania wyjątków. Prawdziwy koszt wyrzucenia wyjątku jest poza tematem. Na ten temat jest dużo informacji. Dość powiedzieć, że jest o wiele rzędów wielkości droższa niż kontrola czekowa if (IsDisposed)
. Dla mnie koszt niepotrzebnego rzucania wyjątków jest nie do przyjęcia. W tym przypadku mówię niepotrzebnie, ponieważ mam już rozwiązanie, które nie generuje wyjątków. Ponownie, pozwalając kontynuacji rzucać ObjectDisposedException
nie jest dopuszczalne rozwiązanie i co staram się uniknąć.
Hmya, to standardowy problem DoEvents(). Async/await nie robi nic, aby go rozwiązać, jeśli zamknięcie okna nie spowoduje również zamknięcia aplikacji. Możesz uniemożliwić użytkownikowi zamknięcie okna ... –
@HansPassant Jasne, mógłbym to zrobić. Ale wtedy miałbym tylko garść złych użytkowników czekających na aktualizację formularza, który chcą zamknąć. Również przykład jest znacznie uproszczony. Mogłem mieć wiele zadań, które przechwytywały kontekst UI w danym momencie. Powyższy kod jest tym, co chcę funkcjonalnie, ale teraz mam ten wzór "jeśli nie usposobiony dalej" atakujący moją bazę kodów. – Zer0
'OnFormClosing' możesz ukryć formularz, a następnie wyrzucić go po zakończeniu danych. Chociaż twój kod będzie działał bezużytecznie, chyba że masz wspólny "bool isStopRequested;" (lub coś podobnego). – Loathing