如何用LINQ表达式替代含多await的foreach异步循环?
异步LINQ替代foreach循环的解决方案
我想找个能替代下面foreach语句的LINQ表达式,试了两种方案都有问题,注释里写了结果:第一种返回的是IEnumerable<Task<CompanySetting>>,不是预期的IEnumerable<CompanySetting>;第二种类型对,但用了同步调用,我担心有问题。
代码示例
public async Task<IEnumerable<CompanySettings>> GetClientSettingsAsync() { foreach(var company in await GetParticipatingCompaniesAsync()) { settings.Add(new CompanySettings(company, await GetUpdoxSyncDataAsync(company))); } // a的类型是IEnumerable<Task<CompanySetting>>,不是预期的IEnumerable<CompanySetting> // var a = (await GetParticipatingCompaniesAsync()) // .Select(async x => new CompanySettings(x, await GetUpdoxSyncDataAsync(x))); // return a; // b的类型正确,但属于同步调用,这样有问题吗? // var b = (GetParticipatingCompaniesAsync()).Result // .Select(x => new CompanySettings(x, GetUpdoxSyncDataAsync(x).Result)); //return b; return settings; }
方法签名
private async Task<IEnumerable<UpdoxCompany>> GetParticipatingCompaniesAsync(); private async Task<UpdoxSyncData> GetUpdoxSyncDataAsync(UpdoxCompany company);
正确的异步LINQ实现方式
你需要先用Select生成所有的Task<CompanySettings>任务集合,再通过Task.WhenAll等待所有任务完成,最后将结果转为IEnumerable<CompanySettings>:
public async Task<IEnumerable<CompanySettings>> GetClientSettingsAsync() { var companies = await GetParticipatingCompaniesAsync(); var tasks = companies.Select(async x => new CompanySettings(x, await GetUpdoxSyncDataAsync(x))); var settings = await Task.WhenAll(tasks); return settings; }
原方案问题分析
- 第一种方案:
Select中使用asynclambda时,会返回IEnumerable<Task<CompanySettings>>,直接返回的话上层调用需要处理未完成的任务,不符合方法返回Task<IEnumerable<CompanySettings>>的预期。 - 第二种方案:
.Result会强制同步阻塞线程,在ASP.NET等存在同步上下文的环境中极易引发死锁,同时完全丧失异步代码的性能优势,绝对不推荐使用。
额外说明
如果需要和原foreach保持串行执行(原循环是逐个await,一次只执行一个异步任务),那么LINQ无法直接替代——因为LINQ的Select本身不控制任务执行顺序,这种情况下原foreach的写法反而更清晰。但如果允许并行执行(多数场景下异步并行能提升效率),上面的Task.WhenAll方案就是最优解。
内容的提问来源于stack exchange,提问作者jrandomuser
相关产品推荐
相关产品推荐

