2015-04-21 10 views
12

Ho il seguente codice sincrono:Conversione ciclo per compiti

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

ho cercato di convertirlo in compiti, ma non sono riuscito a farlo. Ho provato a convertirlo utilizzando Task.WhenAll come questo (e l'ho fatto aggiungere asincrona per la firma del metodo):

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

Questo restituisce immediatamente e non esegue il metodo Run(). Quindi ho provato a convertirlo al seguente codice:

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

Questo blocco per sempre. Tuttavia, quando creo un all'interno del ciclo funziona:

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

Se uso invece await Task.Run(() => step.Run()); attende solo la prima e riprende il filo conduttore.

metodo

Il percorso è simile al seguente:

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

Tutte le fasi di implementare un metodo di lavoro() (che è astratto in una classe base). Il mio primo passo è simile al seguente:

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

E il mio secondo passo è simile al seguente:

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

ho semplice creare un'istanza di NoWorkStep e chiamare instance.Run().

Dove si verifica un problema con l'esecuzione dei passaggi con Task.WhenAll?

Edit: Calling codice dopo ho cambiato il metodo Run per async Task RunAsync:

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

async _void_ Run()? – Ewan

+0

Non utilizzare 'new Task'. Il motivo per cui il tuo quarto esempio funziona non è perché "Wait" è all'interno del ciclo - funziona perché stai usando "Task.Run" invece di "new Task". – Luaan

risposta

20

Consente tracciare i problemi con il tuo codice:

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

Ciò restituisce un raffreddore Task, cioè la Task non è stato effettivamente avviato. Al fine di farlo per iniziare si avrebbe bisogno di chiamare:

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

Ma, non si dovrebbe usare new Task in ogni caso, è necessario utilizzare Task.Run.

Se utilizzo, attendo Task.Run (() => step.Run()); attende solo il primo e riprende il thread principale.

Questo perché Run è async void che non può essere atteso. async void è da utilizzare solo nei gestori di eventi di livello superiore, dove chiaramente non è il caso qui.

Se si vuole attendere fino a quando tutti i compiti sono stati completati, si può fare che a seguito:

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

Ciò garantirà tutte le attività hanno completato l'esecuzione una volta RunAsync finiture.

+0

Ho cambiato 'async void Run' in' async Task RunAsync() 'e ho usato il tuo snippet di codice. Chiamo RunAsync usando 'await step.RunAsync();' e sfortunatamente a volte viene stampato dieci volte HERE-2, a volte due volte a volte senza HERE-2. – Sascha

+0

@Sascha Mostrami come invochi quel codice. –

+0

@Sascha Chi chiama 'doIt'? e come? –

6

Sembra che tu non stia iniziando le attività.

Prova:

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

Basta chiamare Task.Run, non è necessario creare un'attività a freddo solo per avviarlo nella riga successiva –

+0

Stavo solo cercando di illustrare il motivo per cui il codice nella domanda non funzionava. – RagtimeWilly

+0

Le attività a freddo erano il vero problema con il codice originale. È facile dimenticare la chiamata a Start e non offrono nulla su 'Task.Run' o' Task.Factory.StartNew '. –

6

È possibile utilizzare Parallel.ForEach.

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

In questo modo non si scherza nemmeno con le parti di livello inferiore di Parallel Framework.

+2

Funzionerebbe e forse finisco con questo, ma dovrebbe funzionare anche lo schema di attesa acuta, e mi piace capire dove ho sbagliato. – Sascha

+0

O effettivamente 'result.AsParallel(). ForAll (step => step.Run());', ma non userete PLINQ se non avete una query effettiva. –

+0

@NathanCooper Hai ragione, dipende dal tipo di risultato se ha senso o meno. Entrambi funzionano, ma mi piace 'ForEach()' qui perché conserva l'intento dello snippet di codice iniziale. – jdphenix