You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:36:46