Niedawno powiedziano mi, że sposób, w jaki korzystam z mojego .ContinueWith for Tasks, nie jest właściwym sposobem ich używania. Nie znalazłem jeszcze dowodów na to w Internecie, więc zapytam was i zobaczę odpowiedź. Oto przykład, jak używam .ContinueWith:
public Task DoSomething()
{
return Task.Factory.StartNew(() =>
{
Console.WriteLine("Step 1");
})
.ContinueWith((prevTask) =>
{
Console.WriteLine("Step 2");
})
.ContinueWith((prevTask) =>
{
Console.WriteLine("Step 3");
});
}
Teraz wiem, że to prosty przykład i będzie działać bardzo szybko, ale załóżmy, że każde zadanie wykonuje dłuższą operację. Więc powiedziano mi, że w .ContinueWith musisz powiedzieć prevTask.Wait (); w przeciwnym razie możesz pracować przed zakończeniem poprzedniego zadania. Czy to w ogóle jest możliwe? Założyłem, że moje drugie i trzecie zadanie zostanie uruchomione dopiero po zakończeniu poprzedniego zadania.
Co mi powiedziano, jak napisać kod:
public Task DoSomething()
{
return Task.Factory.StartNew(() =>
{
Console.WriteLine("Step 1");
})
.ContinueWith((prevTask) =>
{
prevTask.Wait();
Console.WriteLine("Step 2");
})
.ContinueWith((prevTask) =>
{
prevTask.Wait();
Console.WriteLine("Step 3");
});
}
c#
task-parallel-library
Travyguy9
źródło
źródło
Odpowiedzi:
Echh… Myślę, że niektóre z obecnych odpowiedzi czegoś brakuje: co się dzieje z wyjątkami?
Jedynym powodem, dla którego wezwałbyś
Wait
kontynuację, byłoby zaobserwowanie potencjalnego wyjątku od poprzednika w samej kontynuacji. Ta sama obserwacja miałaby miejsce, gdybyś uzyskał dostępResult
w przypadku a,Task<T>
a także w przypadku ręcznego dostępu doException
właściwości. Szczerze mówiąc, nie zadzwoniłbymWait
ani nie uzyskiwał dostępu,Result
ponieważ jeśli istnieje wyjątek, zapłacisz cenę ponownego podniesienia go, co jest niepotrzebnym narzutem. Zamiast tego możesz po prostu odznaczyćIsFaulted
właściwość z poprzednikaTask
. Alternatywnie można tworzyć rozwidlone przepływy pracy, łącząc się w łańcuchy z wieloma rodzeńskimi kontynuacjami, które są uruchamiane tylko na podstawie sukcesu lub niepowodzenia zTaskContinuationOptions.OnlyOnRanToCompletion
iTaskContinuationOptions.OnlyOnFaulted
.Teraz nie jest konieczne obserwowanie wyjątku poprzednika w kontynuacji, ale możesz nie chcieć, aby przepływ pracy posuwał się naprzód, jeśli, powiedzmy, „Krok 1” nie powiódł się. W tym przypadku: podając
TaskContinuationOptions.NotOnFaulted
doContinueWith
połączeń uniemożliwiłyby logikę kontynuacyjny ze nigdy nawet wypalania.Pamiętaj, że jeśli twoje własne kontynuacje nie obserwują wyjątku, osoba, która czeka na zakończenie tego ogólnego przepływu pracy, będzie tą osobą, która go obserwuje. Albo są
Wait
naTask
wyższym poziomie, albo zdecydowali się na własną kontynuację, aby wiedzieć, kiedy jest ukończona. Jeśli jest to drugie, ich kontynuacja wymagałaby zastosowania wspomnianej logiki obserwacji.źródło
TaskContinuationOptions
Używasz go poprawnie.
Źródło: metoda Task.ContinueWith (akcja jako MSDN)
Konieczność wywoływania
prevTask.Wait()
każdegoTask.ContinueWith
wywołania wydaje się dziwnym sposobem powtarzania niepotrzebnej logiki - tj. Robienia czegoś, aby być „super duper pewnym”, ponieważ tak naprawdę nie rozumiesz, co robi określony fragment kodu. Na przykład sprawdzanie wartości zerowej tylko po to, aby rzucić miejsce, wArgumentNullException
którym i tak zostałoby rzucone.Więc nie, ktokolwiek ci powiedział, że jest zły i prawdopodobnie nie rozumie, dlaczego
Task.ContinueWith
istnieje.źródło
Kto Ci to powiedział?
Cytując MSDN :
Jaki byłby cel kontynuowania, gdyby nie czekał na zakończenie poprzedniego zadania?
Możesz nawet przetestować to samodzielnie:
Task.Factory.StartNew(() => { Console.WriteLine("Step 1"); Thread.Sleep(2000); }) .ContinueWith((prevTask) => { Console.WriteLine("I waited step 1 to be completed!"); }) .ContinueWith((prevTask) => { Console.WriteLine("Step 3"); });
źródło
Z MSDN na
Task.Continuewith
Myślę, że sposób, w jaki spodziewasz się, że zadziała w pierwszym przykładzie, jest właściwy.
źródło
Możesz również rozważyć użycie Task.Run zamiast Task.Factory.StartNew.
Wpis na blogu Stephena Cleary'ego i post Stephena Toub , do którego się odwołuje, wyjaśniają różnice. W tej odpowiedzi jest również dyskusja .
źródło
Uzyskując dostęp
Task.Result
, wykonujesz podobną logikę dotask.wait
źródło
Powtórzę to, co wielu już mówiło,
prevTask.Wait()
jest niepotrzebne .Aby uzyskać więcej przykładów, można przejść do Chaining Tasks using Continuation Tasks , jeszcze jedno łącze firmy Microsoft z dobrymi przykładami.
źródło