有没有一种优雅的方法可以完成嵌套Task.WhenAlls的工作?

时间:2018-08-04 15:29:49

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

我经常在代码中拥有一组独立任务,这些任务创建了可以立即启动的其他独立任务的集合,因此从时间上看,如下所示。

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);
}

是否有更优雅的方法来链接嵌套的异步任务?

2 个答案:

答案 0 :(得分:2)

如果Task ATask Bs完成后不需要处理任何内容,则可以返回IEnumerable<Task>,然后等待所有这些。

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的结果,那么我会对您编写的代码感到满意。


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

答案 1 :(得分:2)

我拿了您的基本示例代码并将其充实,直到产生结果:

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

然后我介绍了Microsoft的Reactive Framework(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()调用轻松地将可观察对象转换为任务。还可以观察到实物。这是一个容易更换的替代品,更加优雅。最好的事情是它也使用LINQ语法。让我知道是否需要更多说明。