2015-12-19 15 views
12

Ok, quindi in pratica ho un sacco di attività (10) e voglio iniziarle tutte nello stesso momento e attendere che vengano completate. Al termine, voglio eseguire altri compiti. Ho letto un po 'di risorse di questo, ma non riesco a farlo bene per il mio caso particolare ...Esecuzione di attività in parallelo

Ecco quello che ho attualmente (codice è stata semplificata):

public async Task RunTasks(){ 
var tasks = new List<Task> 
{ 
    new Task(async() => await DoWork()), 
    //and so on with the other 9 similar tasks 
} 

Parallel.ForEach(tasks, task => 
{ 
    task.Start(); 
}); 

Task.WhenAll(tasks).ContinueWith(done=>{ 
    //Run the other tasks 
}); 
} 

//This function perform some I/O operations 
public async Task DoWork(){ 
    var results = await GetDataFromDatabaseAsync(); 
    foreach(var result in results){ 
     await ReadFromNetwork(result.Url); 
    } 
} 

Quindi il mio problema è che quando aspetto che le attività vengano completate con la chiamata WhenAll, mi dice che tutte le attività sono finite anche se nessuna di esse è stata completata. Ho provato ad aggiungere Console.WriteLine nel mio foreach e quando ho inserito l'attività di continuazione, i dati continuano ad arrivare dal mio precedente Task s che non sono davvero finiti.

Cosa sto facendo di sbagliato qui?

+0

ho il sospetto in tutto questo si dispone di una '' task da qualche parte (chiamando il costruttore attività è direttamente un po 'sospetto) e si vuole veramente ad attendere il compito interiore. Puoi usare 'Task.Run' al posto del costruttore di compiti/Start? 'Task.Run' farà lo scartare per te. –

+0

Così si pensa che dovrei fare: compiti var = nuova lista { Task.Run (() => DoWork())} –

risposta

16

Non si dovrebbe quasi mai utilizzare direttamente il costruttore Task. Nel tuo caso quell'attività attiva solo l'attività effettiva che non puoi aspettare.

È possibile chiamare semplicemente DoWork e recuperare un'attività, memorizzarla in un elenco e attendere il completamento di tutte le attività. Significato:

tasks.Add(DoWork()); 
// ... 
await Task.WhenAll(tasks); 

Tuttavia, metodi asincroni eseguire sincrono fino a raggiungere il primo attendono su un compito incompleto. Se ti preoccupi di quella parte troppo tempo quindi utilizzare Task.Run di scaricare ad un altro ThreadPool filo e quindi memorizzare che un'attività nell'elenco:

tasks.Add(Task.Run(() => DoWork())); 
// ... 
await Task.WhenAll(tasks); 
+0

Con * funzionare in modo sincrono fino alla prima await * volevi significa che sarebbe in realtà eseguire async solo quando abbiamo chiamato 'Task.WhenAll'? Grazie! – Alisson

+0

@Alisson Mi riferivo di più alla parte sincrona di 'DoWork' e non al codice che la chiama. – i3arnon

0

Il metodo DoWork è un metodo di I/O asincrono. Significa che non hai bisogno di più thread per eseguirne parecchi, poiché il più delle volte il metodo attende in modo asincrono l'I/O da completare. Un thread è sufficiente per farlo.

public async Task RunTasks() 
{ 
    var tasks = new List<Task> 
    { 
     DoWork(), 
     //and so on with the other 9 similar tasks 
    }; 

    await Task.WhenAll(tasks); 

    //Run the other tasks    
} 

Si dovrebbe quasi never use the Task constructor per creare una nuova attività. Per creare un'attività I/O asincrona, è sufficiente chiamare il metodo async. Per creare un'attività che verrà eseguita su un thread pool di thread, utilizzare Task.Run. È possibile leggere this article per una spiegazione dettagliata di Task.Run e altre opzioni di creazione di attività.

0

Se si desidera eseguire in parallelo quelli del compito in diversi thread utilizzando TPL potrebbe essere necessario qualcosa di simile:

public async Task RunTasks() 
{ 
    var tasks = new List<Func<Task>> 
    { 
     DoWork, 
     //... 
    }; 

    await Task.WhenAll(tasks.AsParallel().Select(async task => await task())); 

    //Run the other tasks 
} 

Questi approccio parallelizzazione solo piccole quantità di codice: la coda del metodo al pool di thread e il ritorno di uno Task incompleto. Inoltre, per una quantità così piccola di parallelizzazione delle attività può essere necessario più tempo rispetto alla semplice esecuzione in modo asincrono. Questo potrebbe avere senso solo se i tuoi compiti eseguono un lavoro più lungo (sincrono) prima della loro prima attesa.

Per la maggior parte dei casi il modo migliore sarà:

public async Task RunTasks() 
{ 
    await Task.WhenAll(new [] 
    { 
     DoWork(), 
     //... 
    }); 
    //Run the other tasks 
} 

A mio parere nel codice:

  1. Si consiglia di non avvolgere il codice in Task prima di passare alla Parallel.ForEach.

  2. È possibile solo awaitTask.WhenAll anziché utilizzare ContinueWith.

5

In sostanza si stanno mescolando due paradigmi asincroni incompatibili; Ad esempio Parallel.ForEach() e async-await.

Per quello che vuoi, fai l'uno o l'altro. Per esempio. puoi semplicemente usare Parallel.For[Each]() e rilasciare l'async-attendi del tutto. Parallel.For[Each]() restituirà solo quando tutte le attività parallele sono complete e sarà quindi possibile passare alle altre attività.

Il codice ha alcune altre questioni troppo:

  • si contrassegna il metodo asincrono ma non attendono in esso (l'attendono si ha è nel delegato, non il metodo);

  • quasi certamente si desidera .ConfigureAwait(false) in attesa, soprattutto se non si sta tentando di utilizzare i risultati immediatamente in un thread dell'interfaccia utente.

Problemi correlati