I'm currently trying to continuously print dots at the end of a line as a form of indeterminate progress, while a large list of Tasks are running, with this code:
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);
Where tasks
is from List<Task> tasks = new List<Task>();
,
and tasks.Add(Task.Run(() => someMethodAsync()));
has occurred 10000's of times.
This code currently works, however, is this the correct way of accomplishing this, and is this the most cost-effective way?
There are certainly several ways this can be solved and one of them is yours. However it is not really a good practice to start long running tasks especially when they do nothing than synchronous waiting (that is Thread.Sleep).
You should consider refactoring your code in a technical and a domain part. The technical part is:
The following code might help to understand this a bit better. It starts four tasks which simulate different async operations and waits for all of them to complete. If this takes longer than 250ms the call of WhenAllEx keeps on calling a lambda for reoccuring progress report.
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);
}
}
As Thomas mentioned, there are certainly several ways to handle this. The one that springs immediately to mind for me is:
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);
Note that this approach does use await
, thus requiring this method to be async
. Usually for console apps, I recommend having a Main
just call MainAsync
, so your blocking (or main loop) is all in one line of code and not mixed with any logic.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With