2016-03-22 5 views
6

Ich versuche gerade kontinuierlich Punkte am Ende einer Zeile als eine Art unbestimmten Fortschritt zu drucken, während eine große Liste von Aufgaben läuft, mit diesem Code:Zeige den Fortschritt während ich auf alle Aufgaben in der Liste <Task> warte

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

Wo tasks von List<Task> tasks = new List<Task>(); ist,
und tasks.Add(Task.Run(() => someMethodAsync())); hat 10000 des mal aufgetreten ist.
Dieser Code funktioniert derzeit, ist dies jedoch der richtige Weg, um dies zu erreichen, und ist dies der kostengünstigste Weg?

Antwort

6

Es gibt sicherlich mehrere Möglichkeiten, dies zu lösen, und einer davon gehört Ihnen. Es ist jedoch nicht wirklich eine gute Übung, lange laufende Aufgaben zu starten, besonders wenn sie nichts tun als synchrones Warten (das ist Thread.Sleep).

Sie sollten Ihren Code in einem technischen und einem Domain-Teil umgestalten. Der technische Teil ist:

  1. Warten Sie, bis alle Aufgaben in einer bestimmten Sammlung
  2. abgeschlossen Wenn das dauert nicht mehr regelmäßig über die Fortschritte

Der folgende Code dieses ein bisschen besser zu verstehen, könnte helfen, berichtet. Es startet vier Aufgaben, die verschiedene asynchrone Vorgänge simulieren und warten, bis alle ausgeführt sind. Wenn dies länger als 250 ms dauert, ruft der Aufruf von WhenAllEx weiterhin ein Lambda zum Wiederholen des Fortschrittsberichts auf.

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

Wird diese Zeile "var whenAllTask ​​= Task.WhenAll (tasks);" blockiert werden, bis alle Aufgaben abgeschlossen sind? und der Code wird immer auf alle Aufgaben warten und danach Task.Delay ausführen? –

+0

Task.WhenAll (...) blockiert nicht, gibt aber eine Aufgabe zurück, die nach Abschluss aller Aufgaben in den Parametern abgeschlossen ist. Das gleiche gilt für Task.Delay. Es gibt eine Aufgabe zurück, die nach 250ms (im Beispielcode) abgeschlossen ist. Der Trick besteht darin, auf eine dritte Aufgabe zu warten, die abgeschlossen wird, wenn entweder Task.WhenAll OR Task.Delay abgeschlossen ist. Ich werde dem Code einige Kommentare hinzufügen. –

+0

Vielen Dank für Ihre ausführliche Antwort! Ich habe zwei Syntaxfragen. Was geschieht Syntax-weise bei dem Unterstrich, den Sie in den Parametern für 'var whenAll' verwendet haben, und was passiert in der Syntax von' for (;;) '? – cloudcrypt

3

Thomas Antwort ist gut, Sie sollten es akzeptieren. Ich werde eine Version mit einem kleineren Code Delta bieten:

des Ersetzen:

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

mit diesem:

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

Das ist effizienter und, ich glaube, sauberen Code.

Die Version von Thomas hatte den zusätzlichen Vorteil, dass der Fortschritts-Task sofort beendet wurde, sobald die "gesamte Aufgabe" abgeschlossen wurde.

+1

Was ist der Vorteil oder der Unterschied bei der Verwendung von 'erwarten Task.Delay (1000)' vs 'System.Threading.Thread.Sleep (1000)'? – cloudcrypt

+0

Auch Syntax-weise, was bedeutet 'async' im Parameter für' Task.Run'? – cloudcrypt

+0

Blockiert nicht. Dies spart einen Thread, der ansonsten ständig damit beschäftigt wäre, zu warten. – usr

9

Wie Thomas erwähnt, gibt es sicherlich mehrere Möglichkeiten, damit umzugehen. Derjenige, der für mich sofort in den Sinn kommt ist:

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

Beachten Sie, dass dieser Ansatz await nicht verwendet, so diese Methode erfordert async zu sein. Normalerweise für Konsolen-Apps, empfehle ich eine Main rufen Sie einfach MainAsync, so dass Ihre Blockierung (oder Hauptschleife) ist alles in einer Zeile Code und nicht mit irgendeiner Logik gemischt.

+0

Ist es möglich, es so zu implementieren, dass wir den tatsächlichen Fortschritt in Prozent der gesamten Aufgabenausführung melden können? Irgendein Ansatz dafür? Leider können Sie keinen Parameter wie 'List > auf' WhenAny' setzen ... und hier haben wir nur eine große Aufgabe 'tlectTask', auf die wir warten müssen, um den Status zu überprüfen. – Legends

+1

@Legends: Progress Berichte können individuell sein (wie Sie es gerade haben), oder sie können kumulativ sein (bewegen Sie "aktuell" in "DoProgress"). Die einzelnen Fortschrittsberichte sagen im Wesentlichen "ein anderer ist getan". Die kumulativen Berichte würden sagen, "diese vielen wurden gemacht". –

Verwandte Themen