异步并等待-如何等待所有任务完成?

时间:2019-08-13 16:29:35

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

好。我制作了一个简单的控制台应用程序,以弄清楚如何完成所有这些工作。一旦基本轮廓开始工作,然后将其应用于实际应用程序。

这个想法是,我们有很多数据库调用要执行,我们知道这将花费很长时间。我们不想(或不必)等待一个数据库调用完成之后再进行下一个。它们都可以同时运行。

但是,在进行所有调用之前,我们需要执行“启动”任务。当所有调用完成后,我们需要执行“完成”任务。

我现在在这里:

static void Main(string[] args)
{
    Console.WriteLine("starting");
    PrintAsync().Wait();        
    Console.WriteLine("ending");  // Must not fire until all tasks are finished
    Console.Read();
}

// Missing an "await", I know. But what do I await for?
static async Task PrintAsync()
{
    Task.Run(() => PrintOne());
    Task.Run(() => PrintTwo());
}

static void PrintOne()
{
    Console.WriteLine("one - start");
    Thread.Sleep(3000);
    Console.WriteLine("one - finish");
}

static void PrintTwo()
{
    Console.WriteLine("two - start");
    Thread.Sleep(3000);
    Console.WriteLine("two - finish");
}

但是无论我怎么尝试,Ending总是被打印得太早:

starting
ending
one - start
two - start
one - finish
two - finish

正确的做法是PrintTwo()在完成PrintOne()之前开始。但是我该如何正确地等待PrintAsync()完成之后再进行其他操作?

2 个答案:

答案 0 :(得分:7)

您需要等待内部任务的结束:

static async Task PrintAsync()
{
    await Task.WhenAll(Task.Run(() => PrintOne()), Task.Run(() => PrintTwo()));
}

说明:async Task表示一种等待的方法。在此方法内,您还可以await个任务。如果您不这样做,那么它将简单地让任务松散,这些任务将自行运行。 Task.Run返回可以等待的Task。如果您希望两个任务并行运行,则可以使用retur值中的任务,并在等待的方法Task.WhenAll

中使用它们

编辑:实际上,Visual Studio会用绿色弯曲线标记此代码。当将鼠标悬停在上面时,您会得到警告:

enter image description here

CS4014

这应该解释为什么在任务完成之前打印"ending"

编辑2:

如果您要迭代的参数集合并调用异步方法以传递参数,则也可以在第一行中使用select语句来实现它:

static async Task DatabaseCallsAsync()
{
    // List of input parameters
    List<int> inputParameters = new List<int> {1,2,3,4,5};  
    await Task.WhenAll(inputParameters.Select(x => DatabaseCallAsync($"Task {x}")));
}

static async Task DatabaseCallAsync(string taskName)
{
    Console.WriteLine($"{taskName}: start");
    await Task.Delay(3000);
    Console.WriteLine($"{taskName}: finish");
}

最后一部分类似于previous answer

答案 1 :(得分:0)

在这里OP。我将Mong Zhu的答案标记为正确,因为它可以将我引向解决方案。但我也想在这里分享最终结果,包括juharr的评论中的出色反馈。这是我想出的:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("starting");
        DatabaseCallsAsync().Wait();            
        Console.WriteLine("ending"); // Must not fire until all database calls are complete.
        Console.Read();
    }

    static async Task DatabaseCallsAsync()
    {
        // This is one way to do it...
        var tasks = new List<Task>();
        for (int i = 0; i < 3; i++)
        {
            tasks.Add(DatabaseCallAsync($"Task {i}"));
        }
        await Task.WhenAll(tasks.ToArray());

        // This is another.  Same result...
        List<int> inputParameters = new List<int> { 1, 2, 3, 4, 5 };
        await Task.WhenAll(inputParameters.Select(x => DatabaseCallAsync($"Task {x}")));
    }

    static async Task DatabaseCallAsync(string taskName)
    {
        Console.WriteLine($"{taskName}: start");
        await Task.Delay(3000);
        Console.WriteLine($"{taskName}: finish");
    }
}

结果如下:

starting
Task 0: start
Task 1: start
Task 2: start
Task 2: finish
Task 0: finish
Task 1: finish
ending