2015-06-25 16 views
9

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ąć.

+0

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 ... –

+0

@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

+0

'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

Odpowiedz

2

Używam również IsDisposed do sprawdzania stanu sterowania w takich sytuacjach. Chociaż jest trochę gadatliwy, nie jest bardziej rozwlekły, niż jest to konieczne, aby poradzić sobie z sytuacją - i wcale nie jest mylący. Funkcjonalny język taki jak F # z monadami mógłby prawdopodobnie pomóc - nie jestem ekspertem - ale wydaje mi się, że jest tak dobry jak w C#.

2

Powinno być całkiem proste posiadanie CancellationTokenSource, którego właścicielem jest Twój formularz, i wywołanie formularza Cancel po jego zamknięciu.

Wówczas twoje metody async mogą obserwować CancellationToken.

+0

To jest właściwy sposób, aby to zrobić. Również każda logika powinna być uwzględniona w formularzu (i testowanej jednostce). Procedury obsługi zdarzeń formularza powinny być puste i powinny wywoływać proste metody asynchroniczne w modelu widoku lub usługi, przekazując metodę tokenu anulowania. – jnm2

+1

Myślałem o tym, ale martwiłem się o ewentualny stan wyścigu. Tak więc w "FormClosing" na tokenie wołam "Anuluj". Ale 'Task' już ukończył i opublikował jego kontynuację (reszta metody po 'aait') przechwyconego' SynchronizationContext'. Który w tym przypadku siedzi w kolejce komunikatów i nadal będzie działał. Przypuszczam, że mogę to łatwo przetestować, przesadnie nasycając kolejkę wiadomości. – Zer0

+0

Tu rzeczywiście łatwo odtworzyć stan wyścigu. Jest to gorsze niż zarówno mój projekt, jak i sugestia zapobiegania zamykaniu formularza, ponieważ teraz są nieobsługiwane wyjątki. Mogę opublikować kod, którego użyłem do przetestowania, jeśli chcesz. – Zer0

0

Rozwiązałem kiedyś podobny problem, nie zamykając formularza. Zamiast tego ukryłem to na początku i naprawdę zamknąłem dopiero, gdy zakończono wszystkie zaległe prace. Musiałem oczywiście śledzić tę pracę w postaci zmiennych Task.

Uważam, że jest to czyste rozwiązanie, ponieważ problemy z usuwaniem się w ogóle nie pojawiają. Użytkownik może jednak natychmiast zamknąć formularz.

Powiązane problemy