Jakie są różnice między używaniem Parallel.ForEach lub Task.Run () do asynchronicznego uruchamiania zestawu zadań?
Wersja 1:
List<string> strings = new List<string> { "s1", "s2", "s3" };
Parallel.ForEach(strings, s =>
{
DoSomething(s);
});
Wersja 2:
List<string> strings = new List<string> { "s1", "s2", "s3" };
List<Task> Tasks = new List<Task>();
foreach (var s in strings)
{
Tasks.Add(Task.Run(() => DoSomething(s)));
}
await Task.WhenAll(Tasks);
c#
async-await
parallel.foreach
Petter T.
źródło
źródło
Task.WaitAll
zamiastTask.WhenAll
.Odpowiedzi:
W takim przypadku druga metoda będzie asynchronicznie czekać na zakończenie zadań zamiast blokowania.
Jednak użycie
Task.Run
pętli ma tę wadęParallel.ForEach
, że jestPartitioner
tworzona, aby uniknąć wykonywania większej liczby zadań niż to konieczne.Task.Run
zawsze utworzy jedno zadanie na element (ponieważ to robisz), aleParallel
partie klas działają, więc utworzysz mniej zadań niż wszystkie elementy pracy. Może to zapewnić znacznie lepszą ogólną wydajność, zwłaszcza jeśli treść pętli wymaga niewielkiej ilości pracy na element.W takim przypadku możesz połączyć obie opcje, pisząc:
Zauważ, że można to również zapisać w tej krótszej formie:
źródło
DoSomething
takasync void DoSomething
?async Task DoSomething
?Pierwsza wersja będzie synchronicznie blokować wątek wywołujący (i uruchamiać na nim niektóre zadania).
Jeśli jest to wątek interfejsu użytkownika, spowoduje to zawieszenie interfejsu użytkownika.
Druga wersja będzie uruchamiać zadania asynchronicznie w puli wątków i zwalniać wątek wywołujący, dopóki nie zostaną wykonane.
Istnieją również różnice w stosowanych algorytmach planowania.
Zwróć uwagę, że drugi przykład można skrócić do
źródło
await Task.WhenAll(strings.Select(async s => await Task.Run(() => DoSomething(s)));
? Miałem problemy podczas zwracania zadań (zamiast czekania), zwłaszcza gdy instrukcje takie jakusing
były używane do usuwania obiektów.Skończyło się na tym, że to było łatwiejsze do odczytania:
źródło
Widziałem użycie Parallel.ForEach niewłaściwie i pomyślałem, że przykład w tym pytaniu może pomóc.
Po uruchomieniu poniższego kodu w aplikacji konsoli zobaczysz, jak zadania wykonywane w Parallel.ForEach nie blokują wątku wywołującego. Może to być w porządku, jeśli nie zależy Ci na wyniku (dodatnim lub ujemnym), ale jeśli potrzebujesz wyniku, powinieneś upewnić się, że używasz Task.WhenAll.
Oto wynik:
Wniosek:
Użycie Parallel.ForEach z Task nie spowoduje zablokowania wątku wywołującego. Jeśli zależy Ci na wyniku, czekaj na zadania.
~ Pozdrawiam
źródło