运行 接口列表中的并行异步函数

Running async functions in parallel from list of interfaces

我有一个与 类似的问题,因为我希望 运行 并行处理函数列表中的多个函数。

我在许多在线评论中注意到,如果您的方法中有另一个 await,Task.WhenAll() 将无济于事,因为异步方法不是并行的。

然后我继续为每个使用以下函数调用的线程创建一个线程(并行函数的数量通常很小,通常为 1 到 5 个):

public interface IChannel
{
    Task SendAsync(IMessage message);
}

public class SendingChannelCollection
{
    protected List<IChannel> _channels = new List<IChannel>();

    /* snip methods to add channels to list etc */

    public async Task SendAsync(IMessage message)
    {
        var tasks = SendAll(message);

        await Task.WhenAll(tasks.AsParallel().Select(async task => await task));
    }

    private IEnumerable<Task> SendAll(IMessage message)
    {
        foreach (var channel in _channels)
            yield return channel.SendAsync(message, qos);
    }
}

我想仔细检查一下我没有做任何可怕的代码气味或错误,因为我掌握了我从网上找到的修补程序。非常感谢。

让我们比较一下您的线路的行为:

await Task.WhenAll(tasks.AsParallel().Select(async task => await task));

对比于:

await Task.WhenAll(tasks);

在第一种情况下,您要委托给 PLINQ 什么?只有 await 操作,它基本上什么都不做 - 它调用 async/await 机器等待一个 task。因此,您正在设置一个 PLINQ 查询,它完成分区和合并操作结果的所有繁重工作,总计 "do nothing until this task completes"。我怀疑那是你想要的。

If you have another await in your methods, Task.WhenAll() will not help as Async methods are not parallel.

除了问题本身下的一条评论外,我在链接问题的任何答案中都找不到。我想说这可能是一种误解,源于 async/await 不会神奇地将您的代码转换为并发代码这一事实。但是,假设您处于没有自定义 SynchronizationContext 的环境中(因此不是 ASP 或 WPF 应用程序),async 函数的延续将安排在线程池上,并且可能 运行 并行。我会委托 来阐明这一点。这基本上意味着如果你的 SendAsync 看起来像这样:

Task SendAsync(IMessage message)
{
    // Synchronous initialization code.

    await something;

    // Continuation code.
}

然后:

  • 同步等待 运行s 之前的第一部分。如果这部分是重量级的,应该在SendAll中引入并行,这样初始化代码运行并行
  • await 照常工作,等待工作完成而不用完任何线程。
  • 延续代码将在线程池上安排,因此如果几个 await 同时完成它们的延续 可能 是 运行 并行 if 线程池中有足够的线程。

以上所有假设 await something 实际上是异步等待。如果 await something 有可能同步完成,则后续代码也将 运行 同步完成。

现在有一个陷阱。在问题中,您链接了其中一个答案:

Task.WhenAll() has a tendency to become unperformant with large scale/amount of tasks firing simultaneously - without moderation/throttling.

现在我不知道那是不是真的,因为我找不到任何其他来源声称。我想这是可能的,在这种情况下,调用 PLINQ 来为您处理分区和节流实际上可能是有益的。但是,您说您通常处理 1-5 个函数,所以您不必担心这个。

总而言之,并行很难,正确的方法取决于您的 SendAsync 方法的具体情况。如果它有重量级的初始化代码并且这就是你想要并行化的代码,你应该 运行 所有对 SendAsync 的调用并行。否则,async/await 无论如何都会隐式使用线程池,因此您对 PLINQ 的调用是多余的。