我开始关注异步/等待编程模型并尝试理解模式以及如何使用它们。
鉴于我有一个返回Task<List<string>> GetListAsync()
如何编写我自己的FirstOrDefault()方法
Task<string> result = GetListAsync().FirstOrDefaultAsync();
这感觉不对:
var resultList = await GetListAsync();
var firstElementTask = Task.FromResult(resultList.FirstOrDefault());
有什么方法可以打破async / await编程模型?
答案 0 :(得分:4)
当您的方法返回Task<List<string>>
时,创建FirstOrDefaultAsync
并没有多大意义。你当然可以,但它没有多大帮助。
因为在任务完成之前您无法获得List<string>
。任务完成后,您将留下内存集合中的List<string>
。它拥有您需要的所有元素。此时,您只需拨打yourList.FirstOrDefault()
。
另一方面,当您拥有FirstOrDefaultAsync
或List<Task<string>>
时,有必要创建IEnumerable<Task<string>>
。
public async Task<T> FirstOrDefaultAsync<T>(IEnumerable<Task<T>> tasks)
{
var first = await Task.WhenAny(tasks);
return await first;
}
正如@ i3arnon在评论中指出的那样,这个方法返回任务的结果,无论哪个先发生。不是列表中的第一个。
答案 1 :(得分:3)
您应该意识到将FirsOrDefaultAsync
置于GetListAsync
之上意味着当异步操作完成时,将实现整个列表。 (即Task<List<string>>
已完成并导致List<string>
)
即使拥有FirsOrDefaultAsync
,也没有真正的用处。它总是等同于:
var list = await GetListAsync();
var result = list.FirstOrDefault();
拥有真正的异步FirsOrDefaultAsync
的唯一方法是让提供者支持只异步查询第一个项而不是整个列表。
LINQ和async-await
之间存在固有冲突,因为它们都依赖编译器将代码转换为状态机。这就是Reactive Extension存在的原因(可能值得一试)。另一个很好的解决方案可以在MongoDB C#驱动程序中找到,他们将IEnumerable
重新发送为IAsyncCursor
。
如果可以,你想要的只是辅助方法,那么就是这样:
public async Task<string> FirstOrDefaultAsync()
{
var list = await GetListAsync();
return list.FirstOrDefault();
}
答案 2 :(得分:1)
您的方法返回Task<List<x>>
,因此当任务完成时,您会获得一个列表。期。第二个事实:您正在使用等待获取列表。这使得调用方法已经异步。您不需要在任务中手动包装任何内容:
var resultList = await GetListAsync()
return resultList.FirstOrDefault();
应该是它。
然而,这当然是STILL获取整个列表。但这是&#34; GetList&#34;设计中的一个缺陷。方法。由于它返回一个列表,因此没有什么可以做的。除非List具有延迟实现,否则它将始终返回整个列表。
如果您可以控制该片段代码,您可以尝试重写GetListAsync以返回IEnumerable / IQueryable并确保它们被懒惰处理,但这是另一个主题。