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

异步加载API数据至ListView时搜索卡顿的优化方案咨询

优化搜索时API请求卡顿的解决方案

我之前也遇到过类似的批量API请求导致UI卡顿的问题,结合你的代码场景,给你几个实用的优化方向,一步步解决这个问题:


1. 实现请求取消机制,终止无效的搜索请求

当用户快速输入搜索内容时,之前的API请求其实已经没有意义了,我们可以用CancellationToken来取消这些正在进行的请求,避免资源浪费。另外,建议把过时的WebClient换成HttpClient(微软官方推荐,更支持现代异步操作和取消)。

代码示例:

首先在ViewModel中维护一个CancellationTokenSource来管理搜索请求的生命周期:

private CancellationTokenSource _searchCancellationTokenSource;

// 搜索文本变化时触发的方法
public async Task OnSearchTextChanged(string newSearchText)
{
    // 取消之前未完成的请求
    _searchCancellationTokenSource?.Cancel();
    _searchCancellationTokenSource?.Dispose();
    _searchCancellationTokenSource = new CancellationTokenSource();

    // 节流:等待用户停止输入300ms再发起请求,避免每次输入字符都触发搜索
    try
    {
        await Task.Delay(300, _searchCancellationTokenSource.Token);
    }
    catch (OperationCanceledException)
    {
        return; // 用户又输入了新内容,直接返回
    }

    // 获取你的搜索结果列表
    var searchResults = GetFilteredItems(newSearchText);

    foreach (var item in searchResults)
    {
        if (_searchCancellationTokenSource.Token.IsCancellationRequested)
            break; // 检测到取消信号,终止后续请求

        try
        {
            // 调用带取消令牌的API方法
            item.AvgPrice = await GetMarketStatAvgPriceAsync(item.UniqueName, item.Location, _searchCancellationTokenSource.Token);
        }
        catch (OperationCanceledException)
        {
            break; // 请求被取消,直接退出循环
        }
        catch (Exception ex)
        {
            Debug.Print($"加载{item.UniqueName}失败:{ex.Message}");
            item.AvgPrice = "-";
        }
    }
}

然后优化你的API请求方法,支持取消令牌:

// 全局复用HttpClient,避免频繁创建销毁
private static readonly HttpClient _httpClient = new HttpClient();

public static async Task<string> GetMarketStatAvgPriceAsync(string uniqueName, Location location, CancellationToken cancellationToken)
{
    try
    {
        // 先检查缓存
        var cachedItem = MarketStatChartItemList?.FirstOrDefault(i => i.UniqueName == uniqueName);
        if (cachedItem != null && cachedItem.LastUpdate > DateTime.Now.AddHours(-1))
        {
            return GetLatestAvgPriceFromCache(cachedItem, location);
        }

        // 构造API请求URL
        var apiUrl = $"https://www.albion-online-data.com/api/v1/stats/charts/{FormattingUniqueNameForApi(uniqueName)}?date={DateTime.Now:MM-dd-yyyy}";
        
        // 发送带取消令牌的请求
        using var response = await _httpClient.GetAsync(apiUrl, cancellationToken);
        response.EnsureSuccessStatusCode(); // 确保请求成功
        var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
        
        // 反序列化数据
        var chartData = JsonConvert.DeserializeObject<List<MarketStatChartResponse>>(responseContent);

        // 更新缓存
        if (cachedItem == null)
        {
            cachedItem = new MarketStatChartItem
            {
                UniqueName = uniqueName,
                MarketStatChartResponse = chartData,
                LastUpdate = DateTime.Now
            };
            MarketStatChartItemList?.Add(cachedItem);
        }
        else
        {
            cachedItem.LastUpdate = DateTime.Now;
            cachedItem.MarketStatChartResponse = chartData;
        }

        return GetLatestAvgPriceFromCache(cachedItem, location);
    }
    catch (OperationCanceledException)
    {
        throw; // 向上抛出取消异常,让调用者处理
    }
    catch (Exception ex)
    {
        Debug.Print(ex.StackTrace);
        Debug.Print(ex.Message);
        return "-";
    }
}

// 提取重复的缓存数据处理逻辑,减少代码冗余
private static string GetLatestAvgPriceFromCache(MarketStatChartItem cachedItem, Location location)
{
    var locationData = cachedItem.MarketStatChartResponse?.FirstOrDefault(itm => itm.Location == Locations.GetName(location))?.Data;
    if (locationData?.TimeStamps == null || !locationData.TimeStamps.Any())
        return "-";

    var latestTimestamp = locationData.TimeStamps.Max();
    var latestIndex = locationData.TimeStamps.FindIndex(t => t == latestTimestamp);
    return latestIndex != -1 
        ? locationData.PricesAvg[latestIndex].ToString("N", LanguageController.DefaultCultureInfo) 
        : "-";
}

2. 批量请求API,减少网络调用次数

如果你的目标API支持批量查询(比如一次性传入多个uniqueName),这会是最有效的优化方式——把50+个请求合并成1个,大幅降低网络开销和延迟。

假设API支持用逗号分隔多个uniqueName,可以实现批量请求方法:

public async Task<Dictionary<string, string>> GetBatchAvgPricesAsync(IEnumerable<string> uniqueNames, Location location, CancellationToken cancellationToken)
{
    var result = new Dictionary<string, string>();
    var cachedNames = new List<string>();
    var needFetchNames = new List<string>();

    // 先分离缓存中已有的和需要请求的项
    foreach (var name in uniqueNames)
    {
        var cachedItem = MarketStatChartItemList?.FirstOrDefault(i => i.UniqueName == name);
        if (cachedItem != null && cachedItem.LastUpdate > DateTime.Now.AddHours(-1))
        {
            result[name] = GetLatestAvgPriceFromCache(cachedItem, location);
            cachedNames.Add(name);
        }
        else
        {
            needFetchNames.Add(name);
        }
    }

    if (!needFetchNames.Any())
        return result;

    // 构造批量请求URL
    var formattedNames = string.Join(",", needFetchNames.Select(FormattingUniqueNameForApi));
    var apiUrl = $"https://www.albion-online-data.com/api/v1/stats/charts?uniqueNames={formattedNames}&date={DateTime.Now:MM-dd-yyyy}";
    
    using var response = await _httpClient.GetAsync(apiUrl, cancellationToken);
    response.EnsureSuccessStatusCode();
    var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
    
    // 假设API返回的是键为uniqueName的字典,根据实际API调整反序列化逻辑
    var batchData = JsonConvert.DeserializeObject<Dictionary<string, List<MarketStatChartResponse>>>(responseContent);

    foreach (var name in needFetchNames)
    {
        if (batchData.TryGetValue(name, out var chartData))
        {
            var cachedItem = MarketStatChartItemList?.FirstOrDefault(i => i.UniqueName == name);
            if (cachedItem == null)
            {
                cachedItem = new MarketStatChartItem
                {
                    UniqueName = name,
                    MarketStatChartResponse = chartData,
                    LastUpdate = DateTime.Now
                };
                MarketStatChartItemList?.Add(cachedItem);
            }
            else
            {
                cachedItem.LastUpdate = DateTime.Now;
                cachedItem.MarketStatChartResponse = chartData;
            }
            result[name] = GetLatestAvgPriceFromCache(cachedItem, location);
        }
        else
        {
            result[name] = "-";
        }
    }

    return result;
}

3. UI层面优化,提升用户体验

  • 虚拟列表加载:确保你的ListView使用VirtualizingStackPanel(WPF默认支持),它只会加载可见区域的项,避免一次性渲染所有50+项,减少UI线程压力。
  • 加载状态提示:在列表项中添加加载指示器(比如小转圈图标),让用户知道数据正在加载,而不是觉得UI卡顿。
  • 优先级加载:先加载列表顶部的可见项,再异步加载下方的项,让用户先看到部分可用数据。

4. 缓存策略优化

  • 滑动过期:可以把缓存的过期策略从“固定1小时”改成“最后访问后1小时过期”,常用的搜索项会保持缓存,减少重复请求。
  • 缓存清理:当缓存的MarketStatChartItemList数量超过阈值(比如200条),可以清理最久未使用的缓存项,避免内存占用过高。

这些优化组合起来,应该能彻底解决搜索卡顿的问题——取消无效请求避免资源浪费,批量请求减少网络开销,节流减少不必要的触发,UI优化提升用户感知。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 06:49:39