Linq和Async Lambdas

时间:2016-04-06 08:15:28

标签: c# linq asynchronous lambda

以下代码......

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表达式转换为委托类型   &#39; Func&lt; int,bool&gt;&#39;。异步lambda表达式可能返回void,Task或   任务&lt; T&gt;,其中任何一个都无法转换为&#39; Func&lt; int,bool&gt;&#39;。

在线

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

你如何使用Async Lambdas和linq?

4 个答案:

答案 0 :(得分:5)

您无法使用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 :(得分:5)

如果您正在使用一小部分LINQ方法,我建议您关注@YuvalItzchakov's answer,因为它仅依赖于作为基类库一部分提供的组件。

如果需要针对异步序列的丰富查询功能,则可以使用Rx.NET代替。 Rx提供了多种异步序列的LINQ方法,其中一些方法与@@一起使用 - 返回代理,即Task

SelectMany

答案 2 :(得分:3)

  

你如何使用Async Lambdas和linq?

介意我转过身来?你如何希望工作?

每当你开始处理异步流时,就会出现很多关于语义的问题。它不仅像你使用LINQ那样单独使用Where子句。

在这种情况下,您正在寻找某种&#34; async,其中&#34;过滤器应用于同步源序列。异步代码的整个想法是异步操作可能需要不同的时间(并且您希望在该操作正在进行时释放调用线程)。

所以,第一个要回答的问题是&#34; async where&#34; 调用过滤器时。由于源序列是同步的(数组),因此所有输入值都可立即使用。应该&#34; async在哪里&#34;同时为所有元素启动异步过滤器,还是应该一次只处理一个?

如果这是一个真实的&#34;异步,其中&#34;而不是&#34; async any&#34;,下一个问题是结果序列的排序(即 评估结果时)。如果我们同时启动所有异步过滤器,那么它们可以以与它们开始时不同的顺序完成。如果任何异步过滤器返回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");

答案 3 :(得分:0)

很好的答案,但我可以看到使用Yuval Itzchakov的方法增加方法的数量。它很丑,但你可以用......

public static class AsyncExtensions
{
    public static Func<T, bool> Convert<T>(Func<T, Task<bool>> func)
    {
        return t => func(t).Result;
    }
}

并按照这样消费:

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

        if (test.Any(i => AsyncExtensions.Convert((Func<int, Task<bool>>)(async x => await TestIt(x)))(i)))
            Console.WriteLine("Contains numbers > 3");
        else
            Console.WriteLine("Contains numbers < 3");
    }

除了丑陋之外,当然最大的问题是它的阻塞,基本上是同步代码调用异步代码。我仍然会使用Yuval Itzchakov的方法,但我希望有一种方法可以做到这一点,而无需再次为异步实现LINQ。