2017-06-26 43 views
5

Czytanie przez answers to this question skłoniło mnie do zastanowienia się nad tym, co dzieje się w wyjątkowych sytuacjach, gdy oczekiwane zadanie rzuca. Czy wszyscy "klienci" dostrzegają wyjątek? Przyznaję, że mogę pomylić kilka rzeczy tutaj; to jest powód, dla którego proszę o wyjaśnienia.Co się dzieje, gdy wiele równoległych wątków czeka na to samo zadanie, które następnie rzuca?

Przedstawię konkretny scenariusz ... Załóżmy, że mam serwer z globalną kolekcją długo działających instancji Task, uruchamianych przez klientów. Po uruchomieniu jednego lub więcej zadań klient może zapytać o ich postęp i pobrać wyniki, gdy tylko staną się dostępne, a także o wszelkich błędach, które mogą wystąpić.

Zadania same w sobie mogą wykonywać bardzo różne czynności związane z biznesem - zwykle nie ma dwóch takich samych. Jeśli jednak któryś z klientów spróbuje uruchomić to samo, co inne, które wcześniej rozpoczęto, serwer powinien rozpoznać to i "dołączyć" drugiego klienta do istniejącego zadania zamiast buforować nową kopię.

Teraz, za każdym razem każdy klient pyta o stan zadania jest to interesuje, to robi coś wzdłuż tych linii:

var timeout = Task.Delay(numberOfSecondsUntilClientsPatienceRunsOut); 
try 
{ 
    var result = await Task.WhenAny(timeout, task); 
    if (result == timeout) 
    { 
     // SNIP: No result is available yet. Just report the progress so far. 
    } 

    // SNIP: Report the result. 
} 
catch (Exception e) 
{ 
    // SNIP: Report the error. 
} 

W skrócie, to daje zadanie jakiś rozsądny czas, aby zakończyć to, co robi pierwszy , a następnie wraca do zgłaszania bieżących postępów. Oznacza to, że istnieje potencjalnie znaczący czas, w którym wielu klientów może zaobserwować to samo niepowodzenie.

Moje pytanie brzmi: czy zadanie dzieje się podczas tego okna, czy wyjątek jest obserwowany (i obsługiwany) przez wszystkich klientów?

+0

"Zwrócone zadanie zakończy się po zakończeniu któregoś z dostarczonych zadań Zwrócone zadanie zawsze kończy się na stanie" RanToCompletion "z ustawieniem" Wynik "na pierwsze zadanie do wykonania. Jest to prawdą, nawet jeśli pierwszy zadanie do ukończenia zakończyło się stanem anulowania lub błędu. " Jeśli błędy 'task' zostaną zakończone i więcej niż jeden klient będzie oczekiwał na to przez konstrukcję, wszyscy zobaczą to zadanie. Wszystkie z nich otrzymają wyjątek, jeśli spróbują sprawdzić wynik "zadania" (a nie "wyniku"). To nie jest umowa typu "kto pierwszy, ten pierwszy raz". –

+0

Aby uprościć to dalej: jeśli oczekujesz 'Task.Run (() => throw new Exception (DateTime.Now.Ticks.ToString()))' wiele razy z rzędu (z obsługą wyjątków), dostaniesz wyjątek za każdym razem, z tym samym znacznikiem czasu (aby udowodnić, że zadanie nie jest wykonywane wielokrotnie). Jedno błędne zadanie może wytworzyć tyle wyjątków, ile chcesz. –

+2

Interesujące pytanie, ale łatwe do trywialnego sprawdzenia się. – spender

Odpowiedz

4

Task.WhenAny nie rzuci się. Na numer the documentation:

Zwrócone zadanie zostanie zakończone po zakończeniu któregokolwiek z dostarczonych zadań zakończonego . Zwrócone zadanie zawsze kończy się w stanie RanToCompletion z ustawionym wynikiem na pierwsze zadanie do wykonania. Jest to prawdą , nawet jeśli pierwsze zadanie do wykonania zakończyło się stanem Canceled lub Faulted.

Otrzymasz z powrotem timeout lub task.

Jeśli wynikiem jest task, a użytkownik czeka na to (lub otrzyma Task.Result), a task popełnił błąd, to spowoduje to odrzucenie. Nie ma znaczenia, ilu dzwoniących to robi, lub kiedy to robi - próba uzyskania wyniku błędnego zadania zawsze powoduje odrzucenie. Prosty kod do zademonstrowania tego:

var t = Task.Run(() => throw new Exception(DateTime.Now.Ticks.ToString())); 
try { 
    await t; 
} catch (Exception e) { 
    Console.WriteLine(e.Message); 
} 
await Task.Delay(1000); 
try { 
    await t; 
} catch (Exception e) { 
    Console.WriteLine(e.Message); 
} 

Spowoduje to wydrukowanie tego samego znacznika czasu, dwa razy. Zadanie jest uruchamiane tylko raz i ma tylko jeden wynik, który generuje wyjątek za każdym razem, gdy spróbujesz go uzyskać. Jeśli chcesz, możesz mieszać różne wątki lub połączenia równoległe, to nie zmienia wyniku.

Należy pamiętać, że w przypadku przekroczenia limitu czasu nadal istnieje podstawowa możliwość wystąpienia wyścigu: dwa różne zadania/wątki, oba oczekujące na to samo zadanie, mogą uzyskać różne wyniki na await Task.WhenAny(timeout, task), w zależności od tego, które zadanie obserwują ukończ pierwszy. Innymi słowy, nawet jeśli await Task.WhenAny(timeout, task) == timeout, task może nadal mieć błąd w dowolnym punkcie pomiędzy .WhenAny() decydując, że zostało to zrobione, a kontrola ostatecznie wróci do ciebie.Można się tego spodziewać, a twój kod powinien sobie z tym poradzić (w następnej rundzie oczekiwania, .WhenAny() natychmiast powróci z błędnym zadaniem).

+0

Do każdego spadkodawcy: proszę zostawić komentarz wyjaśniający, dlaczego odpowiedź jest błędna lub jak można ją poprawić. –