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

C#异步任务未全部执行及字典重复键问题求助

问题分析与修复方案

核心问题原因

1. 任务未全部完成、字典条目不足

  • HttpClient滥用:每个任务创建新HttpClient实例,会导致TCP连接耗尽,部分请求因无法建立连接而失败,且未处理失败场景,失败请求不会写入字典。
  • 未处理异常:请求过程中若发生网络错误、API限流等异常,任务会抛出未捕获的异常,虽Task.WhenAll会等待任务完成,但异常会导致该任务的结果无法写入字典。
  • 线程池校验冗余:手动等待线程池可用线程无意义,Task.Run会自动调度线程池资源,该逻辑反而可能阻塞任务创建,导致部分任务未正确启动。

2. "添加已存在键"异常

Dictionary<TKey,TValue>并非线程安全集合,即便currentRequestId是唯一的,多个线程同时执行pi.Add时,会破坏集合内部哈希表结构,引发键重复的错误(实际并非真的重复,而是集合内部状态异常)。


修复后的代码

const int TotalRequests = 50;
static int requestsCount = 0;
static Dictionary<int, string> pi = new();
static readonly object lockObj = new();
// 复用全局HttpClient实例
static readonly HttpClient httpClient = new();

static void Main(string[] args)
{
    var timer = new Stopwatch();
    timer.Start();

    try
    {
        Task.WhenAll(ExecuteRequests()).Wait();
    }
    catch (AggregateException ex)
    {
        // 捕获并打印所有任务异常
        foreach (var innerEx in ex.InnerExceptions)
        {
            Console.WriteLine($"请求异常: {innerEx.Message}");
        }
    }

    timer.Stop();

    foreach (var item in pi.OrderBy(x => x.Key))
        Console.Write(item.Value);

    Console.WriteLine($"\n\n耗时: {timer.ElapsedMilliseconds}ms");
    Console.WriteLine($"有效条目数: {pi.Count}");
}

static List<Task> ExecuteRequests()
{
    var tasks = new List<Task>();

    for (int i = 0; i < TotalRequests; i++)
    {
        tasks.Add(Task.Run(async () =>
        {
            int currentRequestId;
            // 锁保护请求ID生成
            lock (lockObj)
            {
                currentRequestId = requestsCount++;
            }

            try
            {
                var result = await httpClient.GetAsync($"https://api.pi.delivery/v1/pi?start={currentRequestId * 1000}&numberOfDigits=1000");
                result.EnsureSuccessStatusCode(); // 非200状态直接抛出异常

                var json = await result.Content.ReadAsStringAsync();
                var content = JsonSerializer.Deserialize<JsonObject>(json)!["content"]!.ToString();

                // 锁保护字典写入操作
                lock (lockObj)
                {
                    pi.Add(currentRequestId, content);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"请求ID {currentRequestId} 失败: {ex.Message}");
            }
        }));
    }

    return tasks;
}

关键修改说明

  1. 复用HttpClient:全局创建一个HttpClient实例,避免频繁创建销毁导致的连接耗尽问题,同时提升请求效率。
  2. 锁保护字典操作:对pi.Add也添加锁,确保字典的线程安全访问,避免集合内部状态异常。
  3. 异常处理:
    • 添加try-catch捕获单个任务的请求异常,记录失败请求的ID和原因。
    • 使用result.EnsureSuccessStatusCode()将非200状态码转为异常,统一处理失败场景。
    • 外层捕获AggregateException,打印所有任务的异常信息,便于排查问题。
  4. 移除冗余线程池校验:删除手动等待线程池可用线程的逻辑,交由Task.Run自动管理线程调度。

内容的提问来源于stack exchange,提问作者Daniel Mendes

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 17:27:14