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

调用外部ApiCall()时MoveNext()中断致循环终止问题排查

问题:调用ApiCall()后循环中断,不调用则正常执行

我是C#新手,现有代码在不调用ApiCall()函数时,能正常遍历并打印每个query字符串;但调用该函数后,while循环仅执行一次就中断,根本到不了"doc-dispose"的输出。我搞不懂为什么一个没有返回值的方法会影响调用方的循环。

主代码:

using(StreamReader sr = new StreamReader($"Nebraska.json"))
{
    List<string> listOfParks = new List<string>();
    string? line;
    while((line = sr.ReadLine()) != null)
    {
        using JsonDocument documentSplitByLine = JsonDocument.Parse(line);
        JsonElement root = documentSplitByLine.RootElement;
        using(JsonElement.ArrayEnumerator parks = root.EnumerateArray())
            {
            while(parks.MoveNext())
            {
                string name = parks.Current.GetProperty("name").ToString();
                string local = parks.Current.GetProperty("location").ToString();
                string state = parks.Current.GetProperty("state").ToString();

                string thing = "";
                if(local.Contains(","))
                {
                    thing = local.Substring(0, local.IndexOf(','));
                }
                else if(local.Contains(" and "))
                {
                    thing = local.Substring(0, local.IndexOf(" and "));
                }
                else if(local.Contains("-"))
                {
                    thing = local.Substring(0, local.IndexOf("-"));
                }
                else if(local.Contains("("))
                {
                    thing = local.Substring(0, local.IndexOf("("));
                }
                else if(name.Contains("("))
                {
                    name = name.Substring(0, name.IndexOf("("));
                }
                else if(name.Contains(" -"))
                {
                    name = name.Substring(0, name.IndexOf(" -"));
                }
                else if(local.All(char.IsDigit))
                {
                    thing = local.Substring(0, local.First(char.IsDigit));
                }
                else if(local.Contains("/"))
                {
                    thing = local.Substring(0, local.IndexOf("/"));
                }
                else{
                    thing = local;
                }
                string query = name+"%20"+thing+"%20"+state;
                Console.WriteLine(query);
                // 调用API时出现问题,注释掉这段代码循环正常
                ApiCall(query);
                
            }
            Console.WriteLine("doc-dispose");
            documentSplitByLine.Dispose();
        }
    }
}

ApiCall()函数实现:

static void ApiCall (string query) 
{

    HttpClient client = new HttpClient();
    string baseAdd = "https://maps.googleapis.com/maps/api/place/findplacefromtext/json?input=";
    string tagAdd = "&inputtype=textquery&fields=formatted_address%2Cname%2Crating%2Cgeometry&key=AIzaSyBr2e34qryWOxc5VrAO6xfod2SboB6mhhc";
    string finalQuery = baseAdd+query+tagAdd;

    Console.WriteLine("Calling web API...");
    var responseTask = client.GetAsync(finalQuery);
    responseTask.Wait();
    if(responseTask.IsCompleted)
    {
        var result = responseTask.Result;
        if(result.IsSuccessStatusCode)
        {
            var messageTask = result.Content.ReadAsStringAsync();
            messageTask.Wait();
            JsonDocument documentSplitByLine = JsonDocument.Parse(messageTask.Result);
            JsonElement root = documentSplitByLine.RootElement;
            
            var parks = root.EnumerateObject();
            
            while(parks.MoveNext())
            {
                var park = parks.Current;
                var thing = park.Value[0];
                string name = "" +thing.GetProperty("name");
                string lat = "" +thing.GetProperty("geometry").GetProperty("location").GetProperty("lat");
                string lng = "" +thing.GetProperty("geometry").GetProperty("location").GetProperty("lng");
                string formatted_address = "" +thing.GetProperty("formatted_address");
                LatLng thisLatLng = new LatLng(float.Parse(lat), float.Parse(lng));
                StatePark thisPark = new StatePark(name, thisLatLng, formatted_address);
                thisPark.SayName();

            }
        }
        else
        {
            Console.WriteLine("failed response");
            
        }
    }
}

解决方案及原因分析

核心问题

循环中断是因为ApiCall()中的代码抛出了未捕获的异常,直接终止了程序执行。不调用ApiCall()时没有异常,所以循环正常。具体触发异常的原因有以下几点:

  1. 每次新建HttpClient耗尽套接字资源
    HttpClient是设计用来复用的,每次调用都新建实例会快速耗尽系统套接字,导致后续请求失败抛出异常。

  2. 用.Wait()同步等待异步任务易引发死锁或未处理异常
    .Wait()会阻塞当前线程,如果API请求过程中出现网络超时、JSON解析错误等情况,未捕获的异常会直接终止程序。

  3. JsonDocument未释放导致资源泄漏
    ApiCall()中解析JSON的JsonDocument没有用using包裹,长期运行会占用大量内存,间接引发异常。

修改后的代码

1. 重构ApiCall()为异步方法,复用HttpClient

// 全局复用HttpClient,不要每次调用都新建
private static readonly HttpClient _httpClient = new HttpClient();

static async Task ApiCall(string query) 
{
    string baseAdd = "https://maps.googleapis.com/maps/api/place/findplacefromtext/json?input=";
    string tagAdd = "&inputtype=textquery&fields=formatted_address%2Cname%2Crating%2Cgeometry&key=AIzaSyBr2e34qryWOxc5VrAO6xfod2SboB6mhhc";
    string finalQuery = baseAdd + query + tagAdd;

    Console.WriteLine("Calling web API...");
    try
    {
        // 用await替代Wait(),避免阻塞和死锁
        var result = await _httpClient.GetAsync(finalQuery);
        // 非成功状态码直接抛出异常,进入catch块处理
        result.EnsureSuccessStatusCode();
        
        var responseContent = await result.Content.ReadAsStringAsync();
        
        // 用using包裹JsonDocument,自动释放资源
        using JsonDocument documentSplitByLine = JsonDocument.Parse(responseContent);
        JsonElement root = documentSplitByLine.RootElement;
        
        var parks = root.EnumerateObject();
        while(parks.MoveNext())
        {
            var park = parks.Current;
            // 先判断是否存在索引0的元素,避免索引越界异常
            if (!park.Value.TryGetElement(0, out var thing))
            {
                Console.WriteLine($"API返回结构异常:{park.Name}下无第0个元素");
                continue;
            }
            
            // 安全获取属性,避免属性不存在抛出异常
            if (!thing.TryGetProperty("name", out var nameElem))
            {
                Console.WriteLine("缺少name属性");
                continue;
            }
            string name = nameElem.ToString();
            
            // 解析经纬度用TryParse,避免格式错误导致异常
            if (!thing.TryGetProperty("geometry", out var geometryElem) ||
                !geometryElem.TryGetProperty("location", out var locationElem) ||
                !locationElem.TryGetProperty("lat", out var latElem) ||
                !float.TryParse(latElem.ToString(), out float lat) ||
                !locationElem.TryGetProperty("lng", out var lngElem) ||
                !float.TryParse(lngElem.ToString(), out float lng))
            {
                Console.WriteLine("经纬度解析失败");
                continue;
            }
            
            string formatted_address = thing.GetProperty("formatted_address").ToString();
            
            LatLng thisLatLng = new LatLng(lat, lng);
            StatePark thisPark = new StatePark(name, thisLatLng, formatted_address);
            thisPark.SayName();
        }
    }
    catch (Exception ex)
    {
        // 捕获所有异常,打印信息后继续执行,不会终止循环
        Console.WriteLine($"API调用出错:{ex.Message}");
    }
}

2. 修改主方法为异步,调整循环内的代码

// 主方法改为async Task,支持await调用
static async Task Main(string[] args)
{
    using(StreamReader sr = new StreamReader($"Nebraska.json"))
    {
        List<string> listOfParks = new List<string>();
        string? line;
        while((line = sr.ReadLine()) != null)
        {
            using JsonDocument documentSplitByLine = JsonDocument.Parse(line);
            JsonElement root = documentSplitByLine.RootElement;
            // JsonElement.ArrayEnumerator不需要using,它不实现IDisposable
            var parks = root.EnumerateArray();
            while(parks.MoveNext())
            {
                string name = parks.Current.GetProperty("name").ToString();
                string local = parks.Current.GetProperty("location").ToString();
                string state = parks.Current.GetProperty("state").ToString();

                string thing = local;
                if(local.Contains(","))
                {
                    thing = local.Substring(0, local.IndexOf(','));
                }
                else if(local.Contains(" and "))
                {
                    thing = local.Substring(0, local.IndexOf(" and "));
                }
                else if(local.Contains("-"))
                {
                    thing = local.Substring(0, local.IndexOf("-"));
                }
                else if(local.Contains("("))
                {
                    thing = local.Substring(0, local.IndexOf("("));
                }
                else if(name.Contains("("))
                {
                    name = name.Substring(0, name.IndexOf("("));
                }
                else if(name.Contains(" -"))
                {
                    name = name.Substring(0, name.IndexOf(" -"));
                }
                else if(local.All(char.IsDigit))
                {
                    // 原逻辑有问题:如果local全是数字,Substring(0, local.First(char.IsDigit))会得到空字符串,改为去除开头的数字
                    thing = new string(local.SkipWhile(char.IsDigit).ToArray());
                }
                else if(local.Contains("/"))
                {
                    thing = local.Substring(0, local.IndexOf("/"));
                }
                
                // 用字符串插值更清晰
                string query = $"{name}%20{thing}%20{state}";
                Console.WriteLine(query);
                // 异步调用API,用await等待完成
                await ApiCall(query);
            }
            Console.WriteLine("doc-dispose");
            // using块会自动释放documentSplitByLine,不需要手动调用Dispose()
        }
    }
}

关键修改说明

  • 复用HttpClient:全局只创建一个实例,避免套接字耗尽问题。
  • 异步/await替代.Wait():避免死锁,同时符合C#异步编程规范。
  • 异常捕获:API调用或解析出错时,捕获异常并打印,程序不会终止,循环可以继续执行。
  • 安全解析JSON:用TryGetElement、TryGetProperty、TryParse等方法,避免因数据格式异常导致程序崩溃。
  • 移除多余的using和Dispose():JsonElement.ArrayEnumerator不需要using,using包裹的JsonDocument会自动释放。

内容的提问来源于stack exchange,提问作者Blake Hunter

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 08:15:37