【问题标题】:Running async functions in parallel from list of interfaces从接口列表并行运行异步函数
【发布时间】:2020-03-01 09:53:06
【问题描述】:

我对@9​​87654321@ 有一个类似的问题,我希望从一个函数列表中并行运行多个函数。

我在网上的一些 cmets 中提到,如果您的方法中有另一个 await,Task.WhenAll() 将无济于事,因为 Async 方法不是并行的。

然后我继续为每个 using 函数调用创建了一个线程,如下所示(并行函数的数量通常很小,为 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() 将无济于事,因为 Async 方法不是并行的。 这种说法是错误的。如果异步方法受 CPU 限制,则它们可以是并行的,如果它们受 CPU 限制或 I/O 限制,则绝对可以是并发的。这取决于您如何开始,然后await 相关任务。如果您在创建后立即await 每个任务:没有并发。如果你全部创建它们然后等待它们:并发。

标签: c# multithreading task-parallel-library


【解决方案1】:

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

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

对比:

await Task.WhenAll(tasks);

在第一种情况下,您将什么委托给 PLINQ?只有await 操作,它基本上什么都不做——它调用async/await 机器等待一个task。因此,您正在设置一个 PLINQ 查询,该查询执行分区和合并操作结果的所有繁重工作,这相当于“在此 task 完成之前什么都不做”。我怀疑这就是你想要的。

如果您的方法中有另一个 await,Task.WhenAll() 将无济于事,因为 Async 方法不是并行的。

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

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

    await something;

    // Continuation code.
}

然后:

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

以上所有内容都假设await something 实际上是异步等待的。如果await something 有可能同步完成,那么延续代码也将同步运行。

现在有一个问题。在您链接其中一个答案的问题中:

Task.WhenAll() 在同时触发大规模/大量任务时往往表现不佳 - 没有节制/节流。

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-09-23
    • 2022-11-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多