LINQ 选择异步方法的模拟

Ale*_*bin 2 .net c# linq async-await

假设我有以下示例代码:

private static async Task Main(string[] args)
{
    var result = Enumerable.Range(0, 3).Select(x => TestMethod(x)).ToArray();
    Console.ReadKey();
}

private static int TestMethod(int param)
{
    Console.WriteLine($"{param} before");
    Thread.Sleep(50);
    Console.WriteLine($"{param} after");
    return param;
}
Run Code Online (Sandbox Code Playgroud)

TestMethod 将运行完成 3 次,因此我将看到 3 对beforeafter

0 before
0 after
1 before
1 after
2 before
2 after
Run Code Online (Sandbox Code Playgroud)

现在,我需要使 TestMethod 异步:

private static async Task<int> TestMethod(int param)
{
    Console.WriteLine($"{param} before");
    await Task.Delay(50);
    Console.WriteLine($"{param} after");
    return param;
}
Run Code Online (Sandbox Code Playgroud)

如何为此异步方法编写类似的 Select 表达式?如果我只使用 async lambda Enumerable.Range(0, 3).Select(async x => await TestMethod(x)).ToArray();,那将不起作用,因为它不会等待完成,因此before将首先调用部分:

0 before
1 before
2 before
2 after
0 after
1 after
Run Code Online (Sandbox Code Playgroud)

请注意,我不想并行运行所有 3 个调用 - 我需要它们一个接一个地执行,仅当前一个调用完全完成并返回值时才开始下一个调用。

Dou*_*las 5

我经常遇到这个要求,但我不知道 C# 7.2 有任何内置解决方案可以解决这个问题。我通常只是回退到使用awaita 中的每个异步操作foreach,但您可以使用扩展方法:

public static class EnumerableExtensions
{
    public static async Task<IEnumerable<TResult>> SelectAsync<TSource, TResult>(
        this IEnumerable<TSource> source,
        Func<TSource, Task<TResult>> asyncSelector)
    {
        var results = new List<TResult>();
        foreach (var item in source)
            results.Add(await asyncSelector(item));
        return results;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后您可以await致电SelectAsync

static async Task Main(string[] args)
{
    var result = (await Enumerable.Range(0, 3).SelectAsync(x => TestMethod(x))).ToArray();
    Console.ReadKey();
}
Run Code Online (Sandbox Code Playgroud)

这种方法的缺点是急于求成SelectAsync,而不是懒惰。C# 8 承诺引入异步流,这将允许它再次变得懒惰。