.NET 6中异步Lambda排序IEnumerable返回Task而非User的问题
异步Lambda在OrderByDescending中导致的类型错误问题
问题描述
我在为Discord机器人的小游戏制作排行榜时遇到问题。背景:我有一个小型的User对象集合users,想要获取其中'sushi'(一种分数)数量排名前10的用户。该sushi数据需从数据库单独获取(无需担心性能,数据库在本地SSD,用户集合已精简)。
我的代码如下:
IEnumerable<User> topUsers = users.OrderByDescending(async x => await FishEngine.GetItem(x, "sushi")).Take(10);
非异步方式下,按User对象本地存储的属性排序时一切正常,但使用上述异步代码后,foreach遍历topUsers时抛出异常,显示集合中不是User而是Task。
明明已经使用await,为何会出现这种情况?该如何解决?
PS:我知道通过SQL查询可以更快解决,但我希望理解异步Lambda的错误所在。
补充:GetItem方法代码:
public static async Task<int> GetItem(User dbUser, string name) { return await UserEngine._interface.FetchInventoryItem(dbUser, name); }
该方法在其他场景运行正常,无需深入探究内部实现。
错误原因
OrderByDescending不支持异步Lambda:当你给OrderByDescending传入异步Lambda时,这个Lambda的返回值是Task<int>,而非你期望的int。OrderByDescending会把Task<int>当作排序的键值,而不是等待Task完成后提取内部的int值,最终返回的集合类型实际是IOrderedEnumerable<Task<int>>,和你声明的IEnumerable<User>类型不匹配,遍历自然会出错。- Lambda内的
await仅作用于内部调用:你在Lambda里写的await FishEngine.GetItem(...)只是让Lambda内部等待这个异步方法完成,但Lambda本身仍然是一个返回Task<int>的异步委托,OrderByDescending无法处理这种异步键值的生成逻辑。
解决方法
正确的做法是先批量获取所有用户的sushi分数并与User对象绑定,等待所有异步操作完成后再进行排序:
- 批量获取用户与对应sushi分数的配对集合:
var userSushiPairs = await Task.WhenAll( users.Select(async user => new { User = user, SushiCount = await FishEngine.GetItem(user, "sushi") }) );
- 对配对集合排序并提取前10个User对象:
IEnumerable<User> topUsers = userSushiPairs .OrderByDescending(pair => pair.SushiCount) .Take(10) .Select(pair => pair.User);
这个逻辑的核心是先用Task.WhenAll等待所有异步获取分数的操作完成,得到包含User和对应分数的完整数据集合,之后再用同步的排序方法处理,就能得到正确的User对象排行榜。
内容的提问来源于stack exchange,提问作者Markski
相关产品推荐
相关产品推荐

