Próbuję utworzyć asynchroniczną aplikację konsolową, która wykonuje trochę pracy nad kolekcją. Mam jedną wersję, która używa równoległej pętli dla innej wersji, która używa async / await. Spodziewałem się, że wersja async / await będzie działać podobnie do wersji równoległej, ale jest wykonywana synchronicznie. Co ja robię źle?
class Program
{
static void Main(string[] args)
{
var worker = new Worker();
worker.ParallelInit();
var t = worker.Init();
t.Wait();
Console.ReadKey();
}
}
public class Worker
{
public async Task<bool> Init()
{
var series = Enumerable.Range(1, 5).ToList();
foreach (var i in series)
{
Console.WriteLine("Starting Process {0}", i);
var result = await DoWorkAsync(i);
if (result)
{
Console.WriteLine("Ending Process {0}", i);
}
}
return true;
}
public async Task<bool> DoWorkAsync(int i)
{
Console.WriteLine("working..{0}", i);
await Task.Delay(1000);
return true;
}
public bool ParallelInit()
{
var series = Enumerable.Range(1, 5).ToList();
Parallel.ForEach(series, i =>
{
Console.WriteLine("Starting Process {0}", i);
DoWorkAsync(i);
Console.WriteLine("Ending Process {0}", i);
});
return true;
}
}
c#
.net
async-await
Satish
źródło
źródło
Ending Process
powiadomienie, nie oznacza to , że zadanie się kończy. Wszystkie te powiadomienia są wyrzucane sekwencyjnie zaraz po zakończeniu ostatniego zadania. Zanim zobaczysz komunikat „Zakończenie procesu 1”, proces 1 mógł już dawno się skończyć. Poza wyborem słów, +1.Semaphore
in wDoWorkAsync
celu ograniczenia maksymalnej liczby wykonywanych zadań?Twój kod czeka na zakończenie każdej operacji (użycie
await
) przed rozpoczęciem następnej iteracji.Dlatego nie otrzymujesz żadnej równoległości.
Jeśli chcesz uruchomić istniejącą operację asynchroniczną równolegle, nie potrzebujesz
await
; musisz tylko pobrać kolekcjęTask
si wywołać,Task.WhenAll()
aby zwrócić zadanie, które czeka na wszystkie z nich:return Task.WhenAll(list.Select(DoWorkAsync));
źródło
await
robi dokładnie odwrotność tego, co chcesz - czeka naTask
zakończenie.DoWorkAsync
na każdym elemencielist
(przekazując każdy element doDoWorkAsync
, który zakładam, że ma jeden parametr)?public async Task<bool> Init() { var series = Enumerable.Range(1, 5); Task.WhenAll(series.Select(i => DoWorkAsync(i))); return true; }
źródło
W C # 7.0 możesz użyć nazw semantycznych dla każdego z członków krotki , oto odpowiedź Tima S. przy użyciu nowej składni:
public async Task<bool> Init() { var series = Enumerable.Range(1, 5).ToList(); var tasks = new List<Task<(int Index, bool IsDone)>>(); foreach (var i in series) { Console.WriteLine("Starting Process {0}", i); tasks.Add(DoWorkAsync(i)); } foreach (var task in await Task.WhenAll(tasks)) { if (task.IsDone) { Console.WriteLine("Ending Process {0}", task.Index); } } return true; } public async Task<(int Index, bool IsDone)> DoWorkAsync(int i) { Console.WriteLine("working..{0}", i); await Task.Delay(1000); return (i, true); }
Możesz też pozbyć się
task.
wnętrzaforeach
:// ... foreach (var (IsDone, Index) in await Task.WhenAll(tasks)) { if (IsDone) { Console.WriteLine("Ending Process {0}", Index); } } // ...
źródło