【问题标题】:Async Await In Linq .TakeWhileLinq 中的异步等待 .TakeWhile
【发布时间】:2013-04-27 07:40:13
【问题描述】:

我正在尝试将我的 foreach 函数转换为 linq 函数

这是我的正常代码 [工作正常]

    var tList = new List<Func<Task<bool>>> { Method1, Method2 };
    tList.Shuffle();

    int succeed = 0;
    foreach (var task in tList)
    {
        var result = await task();

        if(!result)
            break;

        succeed += 1;
    }

    MessageBox.Show(succeed == 1 ? "Loading complete." : "Something went wrong!");

这里是转换为 Linq [Giving 2 compiler errors]

    var tList = new List<Func<Task<bool>>> { Method1, Method2 };
    tList.Shuffle();

    int succeed = tList.Select(async task => await task()).TakeWhile(result => result).Count();

    MessageBox.Show(succeed == 1 ? "Loading complete." : "Something went wrong!");

错误

  • 无法将 lambda 表达式转换为委托类型“System.Func,bool>”,因为某些 块中的返回类型不能隐式转换为
    委托返回类型
  • 无法将类型“System.Threading.Tasks.Task”隐式转换为“bool”

我想知道为什么编译器会给出这些消息,所以任何帮助都将不胜感激。

注意:我也尝试了 .TakeWhile(async result =&gt; await result) 的错误

  • 异步方法的返回类型必须是 void、Task 或 Task T

Method1 和 Method2 如果有人想要的话:

public async Task<bool> Method1()
{
    await Task.Delay(1000);
    Console.WriteLine("Method1");
    return false;
}

public async Task<bool> Method2()
{
    await Task.Delay(1000);
    Console.WriteLine("Method2");
    return true;
}

【问题讨论】:

    标签: c# linq


    【解决方案1】:

    一种可能的解决方案

    像这样使用 TakeWhileAsync 扩展方法:

    public static async Task<IEnumerable<T>> TakeWhileAsync<T>(this IEnumerable<Task<T>> tasks, Func<T, bool> predicate)
    {
        var results = new List<T>();
    
        foreach (var task in tasks)
        {
            var result = await task;
            if (!predicate(result))
                break;
    
            results.Add(result);
        }
    
        return results;
    }
    

    必须有一个更优化的解决方案,但我认为这很容易阅读。您遍历任务,等待每个任务完成,然后执行与常规 TakeWhile 方法相同的逻辑。

    首先你需要实际调用你的方法:tList.Select(func =&gt; func(),然后在这个新创建的IEnumerable>上应用扩展方法:tList.Select(func =&gt; func()).TakeWhileAsync(result =&gt; result)

    然后您等待并计算结果:(await tList.Select(func =&gt; func()).TakeWhileAsync(result =&gt; result)).Count()

    为什么你的尝试没有成功

    .Select(async task =&gt; await task()).TakeWhile(result =&gt; result)

    请记住,异步函数总是返回一个任务(或 void)。选择块内的 lambda 返回一个 IEnumerable>。当您对此应用 .TakeWhile 时,您的 TakeWhile 谓词 (result =&gt; result) 也会返回一个 Task,因为此时序列中的每个元素 是一个任务!这会导致错误,因为它应该返回 bool

    .TakeWhile(async result =&gt; await result)

    同样的问题,您的 lambda 返回一个 Task(因为它是一个异步“方法”),它永远不会用作 LINQ 谓词。

    .TakeWhile(result =&gt; result.Result)

    这个(来自西蒙的回答)确实可以编译,甚至可以在某些情况下工作。 (SynchronizationContext 不绑定到单个线程,但这是另一个很长的话题。)然而,主要的收获是阻塞异步代码是危险的,并可能导致死锁。这是一个great detailed article

    【讨论】:

    【解决方案2】:

    第一个和第二个代码之间存在根本区别(它们做的事情不同)。

    在第一个代码中,您正在迭代任务列表。在第二个你只是将每个任务转换为另一个 - 异步 lambda 的返回类型将始终是某事的任务。

    因为您正在按顺序等待它们真正按顺序运行的每个任务。那是你打算做的吗?

    尝试这样的事情(未测试):

    var tList = new List<Func<Task<bool>>> { Method1, Method2 };
    tList.Shuffle();
    
    int succeed = Task.WaitAll(tList.ToArray())
        .TakeWhile(result => result).Count();
    
    MessageBox.Show(
        succeed == 1
        ? "Loading complete."
        : "Something went wrong!");
    

    但这仍然是一段奇怪的代码。

    【讨论】:

    • 我希望它们按顺序运行。
    • 这是您要找的吗? int succeed = tList.Select(task => task.Result).TakeWhile(result => result).Count();或者,可能是这样的: bool succeed = tList.TakeWhile(task => task.Result).Any();
    【解决方案3】:

    这仅仅是因为您无法按照编译器的指示将Task&lt;bool&gt; 转换为bool..。

    改变这个:

    .TakeWhile(result => result)
    

    ..到这个:

    .TakeWhile(result => result.Result)
    

    【讨论】:

    • 但是当我使用 result.Result 时 UI 完全冻结了?有什么想法吗?
    • @RuneS 您的 UI 冻结的原因是因为编译器认为您的方法不是异步的。像 Simon 建议的那样修复代码后,请查看错误列表窗口中的警告“异步方法缺少等待”
    • @Ragzitsu : 不对 Rag 相反,如果我使用 await 它说 bool is not awaitable...
    • @RuneS 您的预期输出是什么? “方法1”、“方法2”和“1”?
    • ..如果您希望它们按顺序运行.. 那您为什么要编写并行运行它们的代码? ..请改写你正在尝试做的事情。
    猜你喜欢
    • 2016-05-02
    • 1970-01-01
    • 2018-11-06
    • 2019-09-17
    • 1970-01-01
    • 2021-10-22
    • 1970-01-01
    • 2015-07-27
    • 2023-03-12
    相关资源
    最近更新 更多