2016-03-22 12 views
6

Attualmente sto provando a stampare continuamente punti alla fine di una riga come una forma di progresso indeterminato, mentre un ampio elenco di attività è in esecuzione, con questo codice:Visualizzazione dell'avanzamento durante l'attesa di tutte le attività nell'elenco <Task> per il completamento

start = DateTime.Now; 
Console.Write("*Processing variables"); 
Task entireTask = Task.WhenAll(tasks); 
Task progress = new Task(() => { while (!entireTask.IsCompleted) { Console.Write("."); System.Threading.Thread.Sleep(1000); } }); 
progress.Start(); 
entireTask.Wait(); 
timeDiff = DateTime.Now - start; 
Console.WriteLine("\n*Operation completed in {0} seconds.", timeDiff.TotalSeconds); 

Dove tasks da List<Task> tasks = new List<Task>();,
e tasks.Add(Task.Run(() => someMethodAsync())); è verificato 10000 s di volte.
Questo codice funziona attualmente, tuttavia, è questo il modo corretto di farlo, ed è questo il modo più economico?

risposta

6

Ci sono sicuramente diversi modi per risolverlo e uno di questi è il tuo. Tuttavia, non è davvero una buona pratica avviare attività a esecuzione prolungata, specialmente quando non fanno nulla rispetto all'attesa sincrona (cioè Thread.Sleep).

È consigliabile prendere in considerazione il refactoring del codice in una parte tecnica e di dominio. La parte tecnica è:

  1. Attendere fino a quando tutte le attività in un dato di raccolta hanno completato
  2. Se questo richiede più fare regolare andamento segnala

Il codice seguente potrebbe aiutare a capire questo un po 'meglio. Inizia quattro attività che simulano diverse operazioni asincrone e attende che tutte le operazioni vengano completate. Se questo richiede più di 250 ms, la chiamata di WhenAllEx continua a chiamare un lambda per il report dei progressi avvenuto.

static void Main(string[] args) 
{ 
    var tasks = Enumerable.Range(0, 4).Select(taskNumber => Task.Run(async() => 
    { 
     Console.WriteLine("Task {0} starting", taskNumber); 
     await Task.Delay((taskNumber + 1) * 1000); 
     Console.WriteLine("Task {0} stopping", taskNumber); 
    })).ToList(); 

    // Wait for all tasks to complete and do progress report 
    var whenAll = WhenAllEx(
     tasks, 
     _ => Console.WriteLine("Still in progress. ({0}/{1} completed)", _.Count(task => task.IsCompleted), tasks.Count())); 

    // Usually never wait for asynchronous operations unless your in Main 
    whenAll.Wait(); 
    Console.WriteLine("All tasks finished"); 
    Console.ReadKey(); 
} 

/// <summary> 
/// Takes a collection of tasks and completes the returned task when all tasks have completed. If completion 
/// takes a while a progress lambda is called where all tasks can be observed for their status. 
/// </summary> 
/// <param name="tasks"></param> 
/// <param name="reportProgressAction"></param> 
/// <returns></returns> 
public static async Task WhenAllEx(ICollection<Task> tasks, Action<ICollection<Task>> reportProgressAction) 
{ 
    // get Task which completes when all 'tasks' have completed 
    var whenAllTask = Task.WhenAll(tasks); 
    for (; ;) 
    { 
     // get Task which completes after 250ms 
     var timer = Task.Delay(250); // you might want to make this configurable 
     // Wait until either all tasks have completed OR 250ms passed 
     await Task.WhenAny(whenAllTask, timer); 
     // if all tasks have completed, complete the returned task 
     if (whenAllTask.IsCompleted) 
     { 
      return; 
     } 
     // Otherwise call progress report lambda and do another round 
     reportProgressAction(tasks); 
    } 
} 
+0

Questa riga "var whenAllTask ​​= Task.WhenAll (tasks);" essere bloccato fino al completamento di tutte le attività? e il codice attenderà sempre che tutte le attività vengano completate e successivamente eseguirà Task.Delay? –

+0

Task.WhenAll (...) non blocca ma restituisce un'attività che termina quando tutte le attività nei parametri sono state completate. Lo stesso per Task.Delay. Restituisce un'attività che termina dopo 250 ms (nel codice di esempio). Il trucco consiste nell'attesa di una terza attività che si completa quando si completa l'attività Task.WhenAll OR Task.Delay. Aggiungerò alcuni commenti al codice. –

+0

Grazie mille per la tua risposta dettagliata! Ho due domande sulla sintassi. Che cosa sta succedendo alla sintassi per la sottolineatura che hai usato nei parametri per 'var whenAll', e cosa sta succedendo nella sintassi di' for (;;) '? – cloudcrypt

3

La risposta di Thomas è buona, dovresti accettarla. Offro una versione con un delta di codice più piccola:

sostituire questo:

Task progress = new Task(() => { while (!entireTask.IsCompleted) { Console.Write("."); System.Threading.Thread.Sleep(1000); } }); 
progress.Start(); 

Con questo:

Task progressTask = Task.Run(async() => { 
while (!entireTask.IsCompleted) { 
    Console.Write("."); 
    await Task.Delay(1000); 
} 
}); 

Questo è più efficiente e, credo, il codice più pulito.

La versione di Thomas aveva il vantaggio aggiuntivo di terminare immediatamente l'attività di avanzamento una volta completato il "wholeTask".

+1

Qual è il vantaggio o la differenza nell'uso di 'await Task.Delay (1000)' vs 'System.Threading.Thread.Sleep (1000)'? – cloudcrypt

+0

Inoltre, in termini di sintassi, qual è il significato di 'async' nel parametro per' Task.Run'? – cloudcrypt

+0

Non blocca. Questo salva un thread che altrimenti sarebbe occupato in attesa tutto il tempo. – usr

9

Come ha detto Thomas, ci sono certamente diversi modi per gestire questo. Quello che viene subito in mente per me è:

start = DateTime.Now; 
Console.Write("*Processing variables"); 
Task entireTask = Task.WhenAll(tasks); 
while (await Task.WhenAny(entireTask, Task.Delay(1000)) != entireTask) 
{ 
    Console.Write("."); 
} 
timeDiff = DateTime.Now - start; 
Console.WriteLine("\n*Operation completed in {0} seconds.", timeDiff.TotalSeconds); 

noti che questo approccio fa uso await, richiedendo quindi questo metodo per essere async. Di solito per le app console, mi raccomando di avere un Main, basta chiamare lo MainAsync, così il tuo blocco (o loop principale) è tutto in una riga di codice e non è mescolato con nessuna logica.

+0

È possibile implementarlo in questo modo, che possiamo riportare il progresso reale in percentuale dell'intera esecuzione dell'attività? Qualche approccio per quello? Sfortunatamente non puoi impostare un parametro come 'Lista > a' WhenAny' ... e qui abbiamo solo un grosso task 'wholeTask'dobbiamo aspettare e controllare il suo stato completato. – Legends

+1

@Legends: Progress i report possono essere individuali (come lo avete attualmente), oppure possono essere cumulativi (spostate 'current' in' DoProgress'). I singoli resoconti sui progressi sostengono essenzialmente che "un altro è fatto". I rapporti cumulativi direbbero "questi sono stati fatti". –

Problemi correlati