2015-04-21 8 views
12

Ich habe folgenden Synchroncode:Konvertieren Schleife Aufgaben

foreach (var step in result) { 
    step.Run(); 
} 

Ich habe versucht, es zu Aufgaben zu konvertieren, aber ich nicht tun. Ich habe versucht, es zu konvertieren Task.WhenAll wie folgt aus (und ich habe async der Methode Signatur anhängen):

var tasks = new List<Task>(); 
foreach (var step in result) { 
    tasks.Add(new Task(() => step.Run())); 
} 
await Task.WhenAll(tasks); 

Dieser kehrt sofort zurück und stellt die Run() Methode nicht ausgeführt werden. Dann habe ich versucht, es in den folgenden Code zu konvertieren:

Dies blockiert für immer. Allerdings, wenn ich ein innerhalb der Schleife erstellen funktioniert es:

foreach (var step in result) { 
    var t = Task.Run(() => step.Run()); 
    t.Wait(); 
} 

Wenn ich stattdessen await Task.Run(() => step.Run()); es wartet nur die ersten und nimmt den Haupt-Thread.

Die run-Methode sieht wie folgt aus:

public async void Run() { 
    var result = Work(); 
    if (null != result && result.Count > 0) { 
     var tasks = new List<Task>(); 
     foreach (var step in result) { 
      await Task.Run(() => step.Run()); 
     } 
    } 
} 

Alle Schritte, um eine Arbeit() Methode implementieren (die in einer Basisklasse abstrakt ist). Mein erster Schritt sieht wie folgt aus:

class NoWorkStep : WorkerStep { 
    protected override IList<WorkerStep> Work() { 
     Console.WriteLine("HERE"); 
     List<WorkerStep> newList = new List<WorkerStep>(); 
     for (int i = 0; i < 10; i++) { 
      newList.Add(new NoWorkStep2()); 
     } 
     return newList; 
    } 
} 

Und meine zweite Schritt sieht wie folgt aus:

class NoWorkStep2 : WorkerStep { 
    protected override IList<WorkerStep> Work() { 
     Console.WriteLine("HERE-2"); 
     return new List<WorkerStep>(); 
    } 
} 

ich einfach eine Instanz von NoWorkStep erstellen und instance.Run() nennen.

Wo habe ich ein Problem mit der Ausführung der Schritte mit Task.WhenAll?

Edit: Code aufrufen, nachdem ich die Run-Methode zu async Task RunAsync geändert:

private static async void doIt() { 
    var step = new NoWorkStep(); 
    await step.RunAsync(); 
} 
+1

async _void_ Ausführen()? – Ewan

+0

Verwenden Sie keine 'neue Aufgabe'. Der Grund, warum Ihr viertes Beispiel funktioniert, liegt nicht daran, dass 'Wait' innerhalb des Zyklus ist - es funktioniert, weil Sie' Task.Run' anstelle von 'neue Aufgabe' verwenden. – Luaan

Antwort

20

Hier können die Probleme mit Ihrem Code kartieren:

new Task(() => step.Run()) 

Das gibt eine kalte Task, was bedeutet, die Task wurde nicht wirklich gestartet. Damit es müssten Sie anrufen starten:

new Task(() => step.Run()).Start) 

Aber Sie nicht new Task sowieso verwenden sollten, sollten Sie Task.Run verwenden.

Wenn ich stattdessen verwenden, warten Task.Run (() => step.Run()); es wartet nur der erste und setzt den Hauptthread fort.

Das ist, weil Runasync void ist, die nicht erwartet werden kann. async void ist nur in Top-Level-Event-Handlern zu verwenden, wo dies hier eindeutig nicht der Fall ist.

Wenn Sie abwarten wollen, bis alle Aufgaben abgeschlossen sind, können Sie tun, dass folgende:

public async Task RunAsync() 
{ 
    var result = Work(); 
    var stepTasks = result.Select(step => Task.Run(() => step.Run())); 
    await Task.WhenAll(steps); 
} 

Dies wird garantieren, dass alle Aufgaben Ausführung einmal RunAsync beendet abgeschlossen haben.

+0

Ich habe 'async void Run' in' async Task RunAsync() 'geändert und dein Code-Snippet benutzt. Ich rufe RunAsync mit 'await step.RunAsync();' an und leider druckt es manchmal zehnmal HERE-2, manchmal zweimal, manchmal auch kein HERE-2. – Sascha

+0

@Sascha Zeigen Sie mir, wie Sie diesen Code aufrufen. –

+0

@Sascha Wer nennt 'doIt'? und wie? –

6

Sie scheinen nicht die Aufgaben zu starten.

Versuchen:

var tasks = new List<Task>(); 

foreach (var step in result) 
{ 
    var t = new Task(() => step.Run()); 
    t.Start(); 
    tasks.Add(t); 
} 

Task.WhenAll(tasks); 
+2

Rufen Sie einfach Task.Run auf, es ist nicht nötig, eine Cold-Task zu erstellen, nur um sie in der nächsten Zeile zu starten. –

+0

Ich habe nur versucht zu veranschaulichen, warum der Code in der Frage nicht funktionierte. – RagtimeWilly

+0

Kalte Aufgaben waren das eigentliche Problem mit dem ursprünglichen Code. Es ist leicht, den Aufruf von Start zu vergessen und sie bieten nichts über 'Task.Run' oder 'Task.Factory.StartNew'. –

6

können Sie Parallel.ForEach verwenden.

Parallel.ForEach(result, step => step.Run()); 

Auf diese Weise können Sie nicht einmal mit den unteren Ebenen des Parallel Frameworks herumspielen.

+2

Das würde funktionieren und vielleicht höre ich damit auf, aber das aync-await-Muster sollte auch funktionieren, und ich mag es zu verstehen, wo ich meinen Fehler habe. – Sascha

+0

Oder in der Tat 'result.AsParallel(). ForAll (step => step.Run());', aber Sie würden PLINQ nur verwenden, wenn Sie eine tatsächliche Abfrage hatten. –

+0

@NathanCooper Sie haben Recht, es hängt von der Art des Ergebnisses ab, ob es Sinn macht oder nicht. Beide funktionieren, aber ich mag 'ForEach()' hier, weil es die Absicht des ursprünglichen Code-Snippets beibehält. – jdphenix

Verwandte Themen