我使用的API客户端是完全异步的,也就是说,每个操作要么返回任务或任务<T>,例如:

static async Task DoSomething(int siteId, int postId, IBlogClient client)
{
    await client.DeletePost(siteId, postId); // call API client
    Console.WriteLine("Deleted post {0}.", siteId);
}

使用c# 5 async/await操作符,启动多个任务并等待它们全部完成的正确/最有效的方法是什么:

int[] ids = new[] { 1, 2, 3, 4, 5 };
Parallel.ForEach(ids, i => DoSomething(1, i, blogClient).Wait());

or:

int[] ids = new[] { 1, 2, 3, 4, 5 };
Task.WaitAll(ids.Select(i => DoSomething(1, i, blogClient)).ToArray());

由于API客户端在内部使用HttpClient,我希望它立即发出5个HTTP请求,每一个请求完成时都写入控制台。


当前回答

所有的答案都是为了运行相同的函数。

下面的代码用于调用不同的函数。只需要把你常规的Task.Run()放在一个数组中,然后调用Task.WhenAll():

await Task.WhenAll(new Task[] { 
    Task.Run(() => Func1(args)),
    Task.Run(() => Func2(args))
});

其他回答

因为你调用的API是异步的,所以Parallel。每个版本都没有多大意义。你不应该在WaitAll版本中使用. wait,因为这会失去并行性。如果调用者是异步的,另一个选择是使用Task。执行Select和ToArray生成任务数组后的WhenAll。第二种选择是使用Rx 2.0

您可以使用任务。WhenAll函数,可以传递任意数量的任务。这个任务。WhenAll返回一个新任务,该任务将在所有任务完成时完成。请确保在Task上异步等待。为了避免阻塞你的UI线程:

public async Task DoSomethingAsync() {
    Task[] tasks = new Task[numTasks];
    for (int i = 0; i < numTasks; i++)
    {
        tasks[i] = DoChildTaskAsync();
    }
    await Task.WhenAll(tasks);
    // Code here will execute on UI thread
}
int[] ids = new[] { 1, 2, 3, 4, 5 };
Parallel.ForEach(ids, i => DoSomething(1, i, blogClient).Wait());

尽管您将这些操作与上面的代码并行运行,但此代码将阻塞每个操作所运行的每个线程。例如,如果网络调用需要2秒,每个线程挂起2秒,不做任何事情,只是等待。

int[] ids = new[] { 1, 2, 3, 4, 5 };
Task.WaitAll(ids.Select(i => DoSomething(1, i, blogClient)).ToArray());

另一方面,上面带有WaitAll的代码也会阻塞线程,在操作结束之前,线程将无法自由处理任何其他工作。

推荐的方法

我更喜欢WhenAll,这将在并行异步执行您的操作。

public async Task DoWork() {

    int[] ids = new[] { 1, 2, 3, 4, 5 };
    await Task.WhenAll(ids.Select(i => DoSomething(1, i, blogClient)));
}

事实上,在上面的例子中,你甚至不需要等待,你可以直接从方法中返回,因为你没有任何延续: DoWork() { Int [] id = new[] {1,2,3,4,5}; 返回Task.WhenAll (id。Select(i => DoSomething(1, i, blogClient)); }

为了支持这一点,这里有一篇详细的博客文章 使用ASP的并发异步I/O的方式和位置。NET Web API

这个问题已经10年了,OP问的是c# 5。

到今天为止,还有一个选择:并行。在。net 6中引入的ForEachAsync方法。

下面是一个基于OP代码的例子:

int[] ids = new[] { 1, 2, 3, 4, 5 };
await Parallel.ForEachAsync(ids, async (i,token) => await DoSomething(1, i, blogClient));

这是完全异步的,不会阻塞任何线程。

此外,它可能比Task更好。WaitAll和Task。WhenAll接近,因为它们不限制并行运行的线程数量。如果你有一个巨大的数组,它会消耗掉你所有的RAM。平行的。ForEachAsync允许你像这样指定并行度:

var options = new ParallelOptions { MaxDegreeOfParallelism = 4 };

await Parallel.ForEachAsync(ids, options, async (i,token) => await DoSomething(1, i, blogClient));

通过这种方式,只有4个线程并行运行。

我只是想在上面所有的答案中补充一点, 如果你写一个库,使用ConfigureAwait是一个很好的实践(false) 得到更好的性能,就像这里说的。

所以这个片段似乎更好:

 public static async Task DoWork() 
 {
     int[] ids = new[] { 1, 2, 3, 4, 5 };
     await Task.WhenAll(ids.Select(i => DoSomething(1, i))).ConfigureAwait(false);
 }

这里有一个完整的小提琴链接。