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

clo*_*ypt 11 .net c# asynchronous task async-await

我正在尝试在一行的末尾连续打印点作为一种不确定的进展形式,同时运行一大堆任务,使用以下代码:

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);
Run Code Online (Sandbox Code Playgroud)

哪里tasksList<Task> tasks = new List<Task>();,
tasks.Add(Task.Run(() => someMethodAsync()));已经发生10000周的时期.
这个代码目前有效,但这是实现这一目标的正确方法,这是最具成本效益的方法吗?

Ste*_*ary 12

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

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);
Run Code Online (Sandbox Code Playgroud)

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


Tho*_*man 9

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

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

  1. 等到给定集合中的所有任务都完成
  2. 如果需要更长时间,请定期进度报告

以下代码可能有助于更好地理解这一点.它启动四个任务,模拟不同的异步操作,并等待所有这些操作完成.如果这花费的时间超过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);
    }
}
Run Code Online (Sandbox Code Playgroud)