【问题标题】:Is there an elegant way to do the equivalent of nested Task.WhenAlls?有没有一种优雅的方式来做相当于嵌套的 Task.WhenAlls?
【发布时间】:2019-01-12 04:46:28
【问题描述】:

我经常有代码,其中我有一组独立任务,这些任务创建可以立即启动的其他独立任务的集合,所以它在时间方面如下所示。

A0 -------
          B0 --------- C0 --------- ...
          B1 ------ C1 ------------ ...
          B2------------------- C2  ...

A1 ---
      B3 -------- C3--------------- ...
      B4  -- C4 ------------------- ...

我所做的通常是嵌套方法,例如

.
.
.
await Task.WhenAll(A0, A1)
.
.
.

private static async ExecuteA(Task A)
{
     var results = await A;
     var BsS = results.Select(r => ExecuteBC(r));
     await Task.WhenAll(BCs);

private static async ExecuteBC(string value)
{
     var result = await ExecuteB(value);
     await ExecuteC(result);
}

有没有更优雅的方式来链接嵌套的异步任务?

【问题讨论】:

  • 我认为你应该从这个问题中删除任务 C 的概念,它似乎与嵌套 WhenAll 无关?
  • 很高兴能有一个minimal reproducible example 来说明您能够实现的目标,这样我们就可以提供复制工作代码的选项。请问可以吗?

标签: c# multithreading asynchronous async-await task-parallel-library


【解决方案1】:

我提取了你的基本示例代码并对其进行了充实,直到它产生了结果:

async Task Main()
{
    string[] a0_source = new[] { "Hello", "World" };
    string[] a1_source = new[] { "Hi", "There" };

    Task<string[]> A0 = ExecuteA(a0_source);
    Task<string[]> A1 = ExecuteA(a1_source);

    var results = await Task.WhenAll(A0, A1);

    var output = String.Join(", ", results.SelectMany(x => x));

    Console.WriteLine(output);
}

private static async Task<string[]> ExecuteA(string[] A)
{
    var BCs = A.Select(r => ExecuteBC(r));
    return await Task.WhenAll(BCs);
}

private static async Task<string> ExecuteBC(string value)
{
    var result = await ExecuteB(value);
    return await ExecuteC(result);
}

private static async Task<string> ExecuteC(string value)
{
    return await Task.Run(() => value + "!C");
}

private static async Task<string> ExecuteB(string value)
{
    return await Task.Run(() => value + "!B");
}

这会在控制台上生成Hello!B!C, World!B!C, Hi!B!C, There!B!C

然后我介绍了微软的反应式框架(NuGet“System.Reactive”)并产生了这个中间结果:

async Task Main()
{
    string[] a0_source = new[] { "Hello", "World" };
    string[] a1_source = new[] { "Hi", "There" };

    IObservable<string> query =
        from a in a0_source.Concat(a1_source).ToObservable()
        from b in Observable.FromAsync(() => ExecuteB(a))
        from c in Observable.FromAsync(() => ExecuteC(b))
        select c;

    var output = String.Join(", ", await query.ToArray());

    Console.WriteLine(output);
}

private static async Task<string> ExecuteC(string value)
{
    return await Task.Run(() => value + "!C");
}

private static async Task<string> ExecuteB(string value)
{
    return await Task.Run(() => value + "!B");
}

然后你可以更进一步,这样做:

async Task Main()
{
    string[] a0_source = new[] { "Hello", "World" };
    string[] a1_source = new[] { "Hi", "There" };

    IObservable<string> query =
        from a in a0_source.Concat(a1_source).ToObservable()
        from b in Observable.Start(() => a + "!B")
        from c in Observable.Start(() => b + "!C")
        select c;

    var output = String.Join(", ", await query.ToArray());

    Console.WriteLine(output);
}

现在看起来相当优雅。最好的一点是,您可以通过简单的 .ToTask() 调用轻松地将 observable 转换为任务。 Observables 也是可等待的。这是一个简单的替换,更优雅。最好的是它也使用 LINQ 语法。如果您需要更多解释,请告诉我。

【讨论】:

    【解决方案2】:

    如果Task ATask Bs 的集合完成后不需要处理任何内容,那么您可以返回一个IEnumerable&lt;Task&gt;,并等待所有这些。

    await Task.WhenAll(ExecuteA(A0).Concat(ExecuteA(A1)))
    
    ...
    
    private static async Task<IEnumerable<Task>> ExecuteA(Task task)
    {
        var results = await task;
        return results.Select(r => ExecuteB(r));
    }
    
    private static async Task ExecuteB(string value)
    {
        ...
    }
    

    如果您需要在 ExecuteA 中处理 ExecuteB 的结果,那么我会对您编写的代码感到满意。


    顺便说一句,我发现将Tasks 传递给方法是不寻常的,并且通常在方法调用之外等待。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-09-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-07
      相关资源
      最近更新 更多