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

ASP.NET Core中如何确保仅返回异步请求的最新结果?

问题描述
  • 使用Syncfusion AutoComplete控件时,每次输入按键都会触发对HomeController.GetGames方法的请求,该方法会调用外部GiantBomb API获取游戏数据
  • 连续输入(如输入"Call of Duty")时,早期请求(如输入"C"的请求)耗时更长,最后返回会覆盖最新请求的结果,导致显示错误数据集
  • 尝试通过Session持久化CancellationTokenSource集合来取消旧请求,但未生效,核心原因是对CancellationTokenSource的序列化存储存在误解
错误原因分析
  • CancellationTokenSource无法序列化:CancellationTokenSource是包含实时状态和回调的复杂对象,无法通过JSON序列化后存储到Session中。反序列化得到的是全新对象,和原对象无关联,调用Cancel()不会对之前的请求产生任何影响
  • 缺少前端请求限流:未对前端请求做防抖处理,导致不必要的频繁请求,加剧了旧请求覆盖新结果的问题
解决方案

一、前端实现防抖逻辑

修改GiantBombAutocomplete_Filtering方法,添加防抖延迟,用户停止输入300ms后再发送请求,减少无效请求数量:

let debounceTimer;
function GiantBombAutocomplete_Filtering(args) {
    clearTimeout(debounceTimer);
    debounceTimer = setTimeout(() => {
        args.preventDefault(); // 阻止默认立即请求
        const autocompleteObj = document.getElementById('GiantBombAutocomplete').ej2_instances[0];
        autocompleteObj.dataSource.executeQuery(args.query)
            .then(e => {
                autocompleteObj.dataSource = e.result;
                autocompleteObj.openPopup();
            })
            .catch(err => console.error(err));
    }, 300); // 可根据需求调整延迟时间
}

二、后端正确实现请求取消(不依赖Session)

改用IMemoryCache存储用户当前活跃的CancellationTokenSource,直接保留对象引用,实现旧请求的有效取消:

1. 控制器注入IMemoryCache

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly IMemoryCache _memoryCache;
    private const string _ctsCacheKeyPrefix = "ActiveCts_";

    public HomeController(ILogger<HomeController> logger, IMemoryCache memoryCache)
    {
        _logger = logger;
        _memoryCache = memoryCache;
    }

    // 保留原有其他方法...

2. 重构GetGames方法

public async Task<IActionResult> GetGames([FromBody]Data dataManager, CancellationToken requestCancellationToken) 
{
    var userId = HttpContext.Session.Id;
    var cacheKey = _ctsCacheKeyPrefix + userId;

    // 取消并清理之前的请求令牌
    if (_memoryCache.TryGetValue(cacheKey, out CancellationTokenSource previousCts))
    {
        previousCts.Cancel();
        previousCts.Dispose();
    }

    // 创建新的关联令牌源
    var currentCts = CancellationTokenSource.CreateLinkedTokenSource(requestCancellationToken);
    _memoryCache.Set(cacheKey, currentCts, new MemoryCacheEntryOptions
    {
        SlidingExpiration = TimeSpan.FromMinutes(5) // 设置过期时间防止内存泄漏
    });

    try
    {
        var games = await GiantBombConnection.GetGames(
            dataManager.where[0].value, 
            currentCts.Token);

        return Json(games);
    }
    catch (OperationCanceledException)
    {
        // 请求被取消时返回空结果
        return Json(new List<Game>());
    }
    finally
    {
        _memoryCache.Remove(cacheKey);
        currentCts.Dispose();
    }
}

3. 简化API调用类的取消逻辑

public class GiantBombConnection
{
    private const string BaseUrl = "https://www.giantbomb.com/api";
    private const string ApiKey = "api key";

    public static async Task<List<Game>> GetGames(string name, CancellationToken cancellationToken)
    {
        var url = $"{BaseUrl}/games";
        var query = HttpUtility.ParseQueryString(string.Empty);

        query["api_key"] = ApiKey;
        query["format"] = "json";
        query["filter"] = $"name:{name}";

        using (var client = new HttpClient())
        {
            var requestMessage = new HttpRequestMessage(HttpMethod.Get, $"{url}?{query}");
            requestMessage.Headers.Add("User-Agent", "MvcMovie - App");

            // 发送请求时传入取消令牌,支持请求过程中取消
            var response = await client.SendAsync(requestMessage, cancellationToken);
            response.EnsureSuccessStatusCode();

            var apiResponse = await response.Content.ReadAsStringAsync(cancellationToken);
            
            cancellationToken.ThrowIfCancellationRequested(); // 检查是否已取消

            var gamesResponse = JsonConvert.DeserializeObject<GamesResponse>(apiResponse);
            return gamesResponse?.Results ?? new List<Game>();
        }
    }
}

三、额外优化建议

  • 请求标识校验:前端给每个请求添加唯一ID,后端返回结果时携带该ID,前端仅处理最新ID对应的结果,即使旧请求未被取消也不会覆盖新结果
  • API结果缓存:对相同搜索关键词的API响应进行缓存,避免重复调用外部API,提升响应速度

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 05:02:15