在等待列表<任务>中的所有任务完成时显示进度

时间:2016-03-22 08:17:22

标签: c# .net asynchronous async-await task

我目前正试图在一行的末尾连续打印点作为一种不确定的进展形式,而大量的任务列表正在运行,使用以下代码:

tasks

List<Task> tasks = new List<Task>();来自tasks.Add(Task.Run(() => someMethodAsync()));
并{{1}}发生了10000次。
这段代码目前有效,但这是实现这一目标的正确方法,这是最具成本效益的方法吗?

3 个答案:

答案 0 :(得分:11)

正如托马斯所说,当然有几种方法可以解决这个问题。我立即想到的那个是:

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

请注意,此方法确实使用await,因此要求此方法为async。通常对于控制台应用程序,我建议Main只调用MainAsync,因此您的阻塞(或主循环)都在一行代码中,而不与任何逻辑混合。

答案 1 :(得分:9)

肯定有几种方法可以解决,其中一种是你的。但是,启动长时间运行的任务并不是一个好习惯,特别是当它们不做同步等待时(即Thread.Sleep)。

您应该考虑在技术和域部分中重构代码。技术部分是:

  1. 等到给定集合中的所有任务都已完成
  2. 如果需要更长时间,请定期进度报告
  3. 以下代码可能有助于更好地理解这一点。它启动四个任务,模拟不同的异步操作,并等待所有这些操作完成。如果这花费的时间超过250毫秒,则WhenAllEx的调用将继续调用lambda来重新执行进度报告。

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

答案 2 :(得分:3)

托马斯&#39;答案很好,你应该接受它。我将提供一个代码增量较小的版本:

替换它:

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

有了这个:

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

这更有效,而且我认为更清晰的代码。

托马斯&#39;一旦&#34; wholeTask&#34;已经完成了。