Linq和异步Lambda

15
以下代码...
using System;
using System.Linq;
using System.Threading.Tasks;

namespace ConsoleAsync
{
    class Program
    {
        static void Main(string[] args)
        {
            MainAsync(args).Wait();
            Console.ReadLine();
        }

        static async Task MainAsync(string[] args)
        {
            int[] test = new[] { 1, 2, 3, 4, 5 };

            if (test.Any(async i => await TestIt(i)))
                Console.WriteLine("Contains numbers > 3");
            else
                Console.WriteLine("Contains numbers <= 3");
        }

        public static async Task<bool> TestIt(int i)
        {
            return await Task.FromResult(i > 3);
        }
    }
}

显示以下错误:-

CS4010: 无法将异步 lambda 表达式转换为委托类型 'Func<int, bool>'。 异步 lambda 表达式可以返回 void、Task 或 Task<T>,其中没有一个可转换为 'Func<int, bool>'。

发生在该行:

if (test.Any(async i => await Test.TestIt(i)))

你如何使用异步Lambda和linq?


1
你不能直接使用原始的LINQ解决这个问题,至少不是直接使用。需要使用Rx。 - Kirill Shlenskiy
4
@KirillShlenskiy觉得可以提交一个答案,像这样说:“你不能...但是这就是你如何使用Rx来实现同样的事情”? - Mick
3个回答

19

使用LINQ时,无法直接实现该功能。但是你可以编写一个小扩展方法来实现:

public static class AsyncExtensions
{
    public static async Task<bool> AnyAsync<T>(
        this IEnumerable<T> source, Func<T, Task<bool>> func)
    {
        foreach (var element in source)
        {
            if (await func(element))
                return true;
        }
        return false;
    }
}

并且像这样使用:

static async Task MainAsync(string[] args)
{
    int[] test = new[] { 1, 2, 3, 4, 5 };

    if (await test.AnyAsync(async i => await TestIt(i))
        Console.WriteLine("Contains numbers > 3");
    else
        Console.WriteLine("Contains numbers <= 3");
}

对我来说,这确实有些繁琐,但它能够达到你的目标。


1
我已经添加了自己的答案,但它更像是对你的答案的评论,因为它根本不是一个答案。 - Mick
1
这段代码将被顺序执行,可以通过在AnyAsync中任何位置引入Task.Delay轻松测试。虽然这不是主要问题,但在引入Async时,同样重要的是任务的并行执行。在我看来,默认情况下,“Observables”在这方面做得更好。 - Mrinal Kamboj

6

如果您只使用了LINQ方法的一个小子集,我建议您遵循@YuvalItzchakov's answer,因为它仅依赖于作为基本类库的一部分可用的组件。

如果需要在异步序列上进行丰富的查询功能,则可以改用Rx.NET。 Rx提供了广泛的LINQ方法,可用于异步序列,其中一些方法与返回Task的委托一起使用,例如SelectMany

IEnumerable<int> numbers = Enumerable.Range(0, 10);

IEnumerable<int> evenNumbers = numbers
    .ToObservable()
    .SelectMany(async i => new { Value = i, IsMatch = await IsEven(i) })
    .Where(a => a.IsMatch)
    .Select(a => a.Value)
    .ToEnumerable();

async Task<bool> IsEven(int i)
{
    await Task.Delay(100);

    return i % 2 == 0;
}

6
你如何处理异步Lambda和Linq?
不介意反过来问吗?你想要它们是如何工作的?
任何时候开始处理异步流,都会涉及到语义方面的许多问题。这不仅仅像在LINQ中打一个Where子句那样简单。
在这种情况下,你正在寻找一种应用于同步源序列的“异步where”筛选器。异步代码的整个思想是异步操作可能需要花费不同的时间(而你希望在该操作正在进行时释放调用线程)。
因此,“异步where”要回答的第一个问题是筛选器何时被调用。由于源序列是同步的(一个数组),所有输入值都是立即可用的。异步where是否应该同时启动所有元素的异步筛选,还是只能逐个处理它们?
如果这是实际的“异步where”而不是“异步any”,则下一个问题是结果序列的排序方式(即评估结果的时间)。如果我们同时启动所有异步筛选器,则它们可以按不同的顺序完成。结果异步序列是否应该在任何异步筛选器返回true时立即产生其第一个值,还是应该将原始值保持在相同的顺序(这意味着缓冲)?
不同的场景需要不同的答案。Rx能够表达这些答案,但它比较难学。Async/await易于阅读,但表达能力较弱。
由于这是一个Any(不像Where那样通用),你只需要回答第一个问题:筛选器是否可以并发运行还是逐个运行?
如果逐个运行,则类似于Yuval的方法即可。
bool found = false;
foreach (var i in test)
{
  if (await TestIt(i))
  {
    found = true;
    break;
  }
}
if (found)
  Console.WriteLine("Contains numbers > 3");
else
  Console.WriteLine("Contains numbers <= 3");

如果过滤器可以并行运行,那么就像这样:
var tasks = test.Select(i => TestIt(i)).ToList();
bool found = false;
while (tasks.Count != 0)
{
  var completed = await Task.WhenAny(tasks);
  tasks.Remove(completed);
  if (await completed)
  {
    found = true;
    break;
  }
}
if (found)
  Console.WriteLine("Contains numbers > 3");
else
  Console.WriteLine("Contains numbers <= 3");

1
使用 Any 方法时,我认为最好按顺序处理每个元素,以避免不必要的处理;而在使用 Where 时,则希望进行并行处理。使用 Linq 进行排序始终是另一个调用,因此 Where 的结果排序可能是任意的。您提出了一个很好的论点,即每个 Linq 方法的异步实现将根据方法的功能而异。 - Mick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接