2013-10-17 11 views
59

Ich versuche, eine asynchrone Konsole App zu erstellen, die einige Arbeiten an einer Sammlung ausführt. Ich habe eine Version, die parallele for-Schleife eine andere Version verwendet, die async/erwartet verwendet. Ich erwartete, dass die async/await-Version ähnlich wie die parallele Version funktioniert, aber synchron ausgeführt wird. Was mache ich falsch?Verwendung in einer Schleife erwarten

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; 
    } 
} 

Antwort

78

Die Art und Weise Sie das await Schlüsselwort verwenden sagt C#, die Sie warten möchten jedes Mal, wenn Sie die Schleife durchlaufen, die nicht parallel ist. Sie können Ihre Methode wie folgt umschreiben, indem Sie eine Liste von Task s und dann await mit Task.WhenAll speichern.

public async Task<bool> Init() 
{ 
    var series = Enumerable.Range(1, 5).ToList(); 
    var tasks = new List<Task<Tuple<int, bool>>>(); 
    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.Item2) 
     { 
      Console.WriteLine("Ending Process {0}", task.Item1); 
     } 
    } 
    return true; 
} 

public async Task<Tuple<int, bool>> DoWorkAsync(int i) 
{ 
    Console.WriteLine("working..{0}", i); 
    await Task.Delay(1000); 
    return Tuple.Create(i, true); 
} 
+2

Ich weiß nicht über andere, aber eine Parallele für Foreach scheint für parallele Schleifen einfacher. – Brettski

+2

Wichtig, wenn Sie sehen, dass die 'Ending Process' Benachrichtigung nicht ** ist, wenn die Aufgabe tatsächlich endet. Alle diese Benachrichtigungen werden nacheinander ausgegeben, unmittelbar nachdem der * letzte * der Aufgaben abgeschlossen ist. Zu dem Zeitpunkt, an dem Sie "Prozess 1 beenden" sehen, war Prozess 1 möglicherweise für eine lange Zeit vorbei. Anders als die Wahl der Wörter dort, +1. –

+0

@Brettski Ich kann falsch liegen, aber eine parallele Schleife schließt alle asynchronen Ergebnisse ein. Durch das Zurückgeben einer Task erhalten Sie sofort ein Task-Objekt zurück, in dem Sie die Arbeit, die gerade ausgeführt wird, verwalten können, z. B. Abbrechen oder Anzeigen von Ausnahmen. Jetzt mit Async/Await können Sie mit dem Task-Objekt freundlicher arbeiten - das heißt, Sie müssen Task.Result nicht ausführen. –

27

Ihr Code wartet für jeden Vorgang (mit await) vor dem Start der nächsten Iteration zu beenden.
Daher erhalten Sie keine Parallelität.

Wenn Sie eine vorhandene asynchrone Operation parallel ausführen möchten, benötigen Sie nicht await; Sie benötigen nur eine Sammlung von Task s zu erhalten und Task.WhenAll() rufen eine Aufgabe zurück, die für alle von ihnen wartet:

return Task.WhenAll(list.Select(DoWorkAsync)); 
+0

so können Sie keine asynchronen Methoden in Loops verwenden? – Satish

+3

@Satish: Sie können. "Warten" ist jedoch genau das Gegenteil dessen, was Sie wollen - es wartet darauf, dass die Aufgabe beendet wird. – SLaks

+0

Ich wollte deine Antwort akzeptieren, aber Tims S hat eine bessere Antwort. – Satish

7
public async Task<bool> Init() 
{ 
    var series = Enumerable.Range(1, 5); 
    Task.WhenAll(series.Select(i => DoWorkAsync(i))); 
    return true; 
} 
Verwandte Themen