在C#中使用Task.WaitAll并行加载API数据并将结果存入列表
解决C#中并行加载多个API Feed并收集结果的问题
你遇到的问题很典型——原来循环里逐个await任务的写法会让它们串行执行,完全浪费了并行的优势。要实现真正的并行并收集所有任务的结果,核心是要跟踪每个任务的返回值,而不是只跟踪任务本身。这里给你两种实用的解决方案:
方案1:基于现有同步LoadDocument的修改
首先,我们需要把任务列表从List<Task>改成List<Task<XDocument>>,这样每个任务都能保留它的返回值。然后用Task.WhenAll(比Task.WaitAll更适合异步方法,避免线程阻塞)等待所有任务完成,最后一次性收集所有结果。
修改后的LoadMultiple方法如下:
public static async Task<List<XDocument>> LoadMultiple(List<long> IDs) { List<XDocument> Documents = new List<XDocument>(); // 改成Task<XDocument>类型的列表,跟踪每个任务的返回值 List<Task<XDocument>> tasks = new List<Task<XDocument>>(); foreach (var ID in IDs) { string url = $"http://Website.com/{ID}.xml?key={API.BUSKEY}"; // 添加返回XDocument的任务到列表 tasks.Add(Task.Run(() => LoadDocument(url))); } // 异步等待所有任务完成,得到所有结果的数组 XDocument[] results = await Task.WhenAll(tasks); // 将结果数组批量添加到Documents列表 Documents.AddRange(results); return Documents; }
关键修改点:
- 使用
Task<XDocument>代替Task:让每个任务执行完成后,我们能直接获取它返回的XDocument。 - 用
await Task.WhenAll(tasks):这是异步等待所有任务完成的标准做法,不会阻塞当前线程,符合async/await的最佳实践。 AddRange(results):一次性把所有结果添加到列表,比逐个添加更高效。
另外,你的Main方法也可以改成异步版本(C# 7.1及以上支持),避免使用.Result可能导致的死锁问题:
static async Task Main(string[] args) { List<long> Ids = new List<long>(); for (int i = 0; i < 1000; i++) { Ids.Add(39027710001); // 注意:数字前不要加0,会被编译器当成八进制数引发错误 } var ListOfDocuments = await LoadMultiple(Ids); }
方案2:优化为全异步加载(更推荐)
你的LoadDocument是同步方法,调用XDocument.Load(URL)会阻塞线程。更好的做法是用HttpClient异步加载流,再解析成XDocument,这样能更高效地利用线程资源:
首先修改LoadDocument为异步方法:
public static async Task<XDocument> LoadDocumentAsync(string url) { // 建议复用HttpClient(比如用静态实例),避免频繁创建销毁实例 using var httpClient = new HttpClient(); using var stream = await httpClient.GetStreamAsync(url); // XDocument.Load没有异步重载,用Task.Run包装同步解析操作 return await Task.Run(() => XDocument.Load(stream)); }
然后修改LoadMultiple方法来调用这个异步方法:
public static async Task<List<XDocument>> LoadMultiple(List<long> IDs) { List<Task<XDocument>> tasks = new List<Task<XDocument>>(); foreach (var ID in IDs) { string url = $"http://Website.com/{ID}.xml?key={API.BUSKEY}"; // 直接添加异步任务,不需要额外Task.Run包装 tasks.Add(LoadDocumentAsync(url)); } // 等待所有异步任务完成并转换为列表 XDocument[] results = await Task.WhenAll(tasks); return results.ToList(); }
为什么推荐这个方案?
- 全异步操作不会阻塞线程池线程,在高并发场景下性能更好。
HttpClient的异步方法是为网络IO优化的,比同步加载更高效。
注意事项
- 绝对不要在循环中逐个
await任务:这会让任务串行执行,完全失去并行的意义。 - 避免在异步方法中使用
Task.WaitAll或.Result:这些是同步阻塞操作,可能导致死锁(尤其是在UI线程或ASP.NET环境中),始终优先用await Task.WhenAll。 - 数字字面量不要加前导0:比如你原来的
039027710001会被编译器当成八进制数,但八进制数不能包含9,会直接报错,所以要去掉前导0。
内容的提问来源于stack exchange,提问作者samtrod
相关产品推荐
相关产品推荐

