C# .NET 6 -如何等待IEnumerated上的异步lambda操作完成

rqqzpn5f  于 2023-01-18  发布在  .NET
关注(0)|答案(3)|浏览(133)

我有问题,使排行榜的不和谐机器人的迷你。
上下文:我有一个'users'的小集合,顾名思义,它包含系统用户的选择,由'User'对象表示。
我想获得这个集合中的前10个用户的'寿司'的数量,如果你愿意的话,一个分数。这个寿司是从一个数据库中单独提取的(不要担心性能,数据库是在SSD上,在同一个盒子上,这个代码将通过的用户集合是非常精简的)

IEnumerable<User> topUsers = users.OrderByDescending(async x => await FishEngine.GetItem(x, "sushi")).Take(10);

如果我是以非异步的方式来做这件事,按照本地存储在用户对象中的任何东西来排序,并且不需要等待,这会非常好地工作,但是当我这样做的时候,foreach中有一个异常,它表明集合topUsers不包含用户,而是似乎是任务的东西。
如果我在等待,为什么会发生这种情况?我应该怎么做才能解决这个问题?
先谢了。
PS,因为我知道有人会这样建议:我知道,在这一点上,通过SQL查询所有内容会更快,无论是作为一种解决方案,还是潜在的perf(尽管正如我在前面指出的,perf在这里不是一个现实的问题),但我真的希望了解我在异步lambda方面做错了什么。
编辑:因为请求了GetItem

public static async Task<int> GetItem(User dbUser, string name)
    {
        return await UserEngine._interface.FetchInventoryItem(dbUser, name);
    }

不要担心会更深入,这个函数在其他地方也使用过,并且工作正常。

jc3wubiy

jc3wubiy1#

诸如OrderBy之类的可枚举方法必须要么转换为SQL(作为查询的一部分),要么对内存中的数据进行操作。解决这个问题的最佳方法是将其转换为SQL,但由于您对另一种方法感兴趣,因此您可以 * 先 * 然后按顺序将所有值加载到内存中。您可以通过创建一个 * task * 序列,然后使用Task.WhenAll来完成此操作:

var getSushiTasks = users.Select(x => FishEngine.GetItem(x, "sushi")).ToList();
var userSushi = await Task.WhenAll(getSushiTasks);
var usersWithSushi = users.Zip(userSushi, (User, Sushi) => (User, Sushi));
var topUsers = usersWithSushi.OrderByDescending(x => x.Sushi).Take(10);

当然,这是非常低效的,因为您要单独加载每个用户的寿司,只是为了比较它们并在内存中取前10名。

mwg9r5ms

mwg9r5ms2#

async x => await FishEngine.GetItem(x, "sushi")几乎与x => FishEngine.GetItem(x, "sushi")相同:await是在异步方法中使用的,所以结果必须以某种方式等待以获得值。async使你的lambda异步,也就是说,它返回Task<User>,而不是你所期望的UserTask<User>在抽象的高层上等价于User,但是它们是不可互换的。对于Task<int>int也是如此:FishEngine.GetItem不返回整数,而是(使用一点JS术语)承诺在以后某个时间点提供整数,因此您必须等待数据库引擎返回的承诺,以便每个用户都能够按寿司对用户进行排序。

5vf7fwbs

5vf7fwbs3#

由于OrderByDescending方法不支持异步lambda表达式,您提供的代码将无法按预期工作。
要根据用户的“sushi”分数获得集合中的前10个用户,可以先使用Task.WhenAll并行获取所有用户的“sushi”分数,然后使用OrderByDescending方法根据用户的“sushi”分数对用户进行排序。最后,使用Take方法获得前10个用户。下面是一个示例:

using System;
using System.Linq;
using System.Threading.Tasks;

class Example
{
    public static async Task Main()
    {
        var users = new List<User> { /* users */ };

        // Fetch sushi scores for all users in parallel
        var sushiTasks = users.Select(async user => new { User = user, Sushi = await FishEngine.GetItem(user, "sushi") });
        var sushiScores = await Task.WhenAll(sushiTasks);

        // Order users by sushi scores and take the top 10
        var topUsers = sushiScores.OrderByDescending(x => x.Sushi).Take(10).Select(x => x.User);

        // Do something with the top users
        foreach (var user in topUsers)
        {
            Console.WriteLine(user.Name);
        }
    }
}

此脚本将使用Task.WhenAll方法并行获取所有用户的寿司得分,然后根据获得的寿司得分对用户进行排序,最后选择前10名用户。
请记住,Task.WhenAll将等待所有任务完成后再继续,这意味着它将并行执行对数据库的所有调用,但将等待所有任务完成后再继续执行下一步。
另外,需要注意的是,基于特定属性对大量项进行排序可能是一项性能密集型任务,如果用户集合非常大,则应考虑这一点。

相关问题