Linq and Async Lambdas



以下代码。。。

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表达式转换为委托类型'函数<int,bool>'。异步lambda表达式可能返回void、Task或任务<T>,其中没有一个可转换为'Func<int,bool>'。

在线

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

如何使用Async Lambdas和linq?

使用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");
}

这确实让我觉得有点麻烦,但它实现了你的目标。

如何使用Async Lambdas和linq?

介意我把它转过来吗?您希望它们如何工作?

每当您开始处理异步流时,都会有很多关于语义的问题。这不仅仅是像使用LINQ那样随意使用Where子句。

在这种情况下,您正在寻找某种应用于同步源序列的"async where"过滤器。异步代码的全部思想是异步操作可能需要不同的时间(并且您希望在操作进行时释放调用线程)。

因此,关于"async where"的第一个问题是,当调用过滤器时。由于源序列是同步的(数组),因此所有输入值都可以立即使用。"async where"应该同时启动所有元素的异步筛选器,还是一次只处理一个?

如果这是一个实际的"async where"而不是"async any",那么下一个问题将是结果序列的排序(即,当评估结果时)。如果我们同时启动所有异步过滤器,那么它们可以按照与启动时不同的顺序完成。当任何异步过滤器返回true时,结果异步序列应该立即产生其第一个值,还是结果序列应该保持原始值的相同顺序(这意味着缓冲)?

不同的场景需要对这些问题给出不同的答案。Rx能够表达这些答案中的任何一个,但学习起来相当困难。Async/await更易于阅读,但表达能力较差。

由于这是一个Any(不像Where那样通用),您只需要回答第一个问题:过滤器是同时运行还是一次运行一个?

如果一次一个,那么像尤瓦尔的方法就会奏效:

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

如果您使用的是LINQ方法的一小部分,我建议遵循@YuvalItzchakov的答案,因为它只依赖于作为基类库一部分的组件。

如果需要通过异步序列提供丰富的查询功能,则可以使用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;
}

最新更新