ASP.NET 超时机制分析

我们常说的接口超时,主要指 WEB请求超时了,即HTTP请求超时了,HTTP请求对应的接口执行时间过长超时了。那么HTTP请求超时了,是我们理解的那样么?今天和大家一起深入剖析下。

一、分类

首先 HTTP请求超时了,这个超时至少有三个层面的超时:

  1. 前端请求定义的超时时间到了;

这个层面的超时,通常前端不会收到任何消息。

当然这和前端使用的框架 还有后端对于超时的处理代码 也有关系。

常见有几种情况: a.空消息返回 例如 使用 jquey 或者 axios

b.继续等待接收后端消息,例如 fetch.

  1. NGINX 或者 其它网关定义的超时时间到了;

这个层面的超时,网关会向接口调用方 发送 HTTP504错误。即前端会收到504的HTTP消息。

  1. 后端服务定义的超时时间到了,比如 Kestrive、Spring Boot 等等。

这个层面的超时,通常需要向前端发送408错误,但是这个消息需要由后端开发人员处理。

二、做法与影响

三个层面逐一分析他们的做法和影响。

1.前端超时,在使用不同框架的时影响还不一样。

1.1 JQUERY 和 AXIOS 这两个框架,会自动计算超时时间,并在超时的时候,主动向后端发送超时信号。超时时间 通过 timeout 这个参数定义。实现机制 通过调用协议层通用组件(例如 XMLHttpRequest ) 的 abort() 方法。

1.2 Fetch 这个框架没有timeout这个参数,没有超时控制。默认就是一直等待后端接口返回数据;

使用这个框架想要达到超时机制,就得自定行写代码计算超时时间是否到来,并在到来后,自行写代码断开连接,并通知后端已超时。实现机制可实现Fetch的AbortControl,底层也是通过调用协议层通用组件的 abort() 方法。

  1. 网关超时

网关超时会向前端发送HTTP 504消息,同时会向后端发送超时信号。前端收到504消息。

  1. 后端超时

此处仅以自己较为熟悉的 ASP.NET 来做分析。

ASP.NET 默认不处理超时机制,即前端调用或者网关定义了超时,且向后端发送了超时信息,我们在代码不做针对性处理的情况,后端代码会继续执行,不受超时信号的影响;

同时,ASP.NET为了后端能够针对超时做出响应,为我们提供了HttpContext.RequestAborted 对象,当前端或者网关发出了超时信号后, HttpContext.RequestAborted 状态值会变化改变,我们通过在接口代码里监听 HttpContext.RequestAborted 状态值 来进行针对性处理,比如 中断代码执行,回滚事务 等等。对于 接口代码里 启用了多线程的情况,多线程的相关方法里 也支持传入此状态值,从而达到控制子线程的目的。这段相对有点抽象,附一段代码给大家参考。

cs 复制代码
        [HttpGet("cycle-delay-with-cancel-check/{seconds}")]
        public async Task<IActionResult> CycleDelayWithCancelCheck(int seconds, [FromQuery]string requestId = null)
        {
            // Generate a unique request ID if not provided
            if (string.IsNullOrEmpty(requestId))
            {
                requestId = Guid.NewGuid().ToString();
            }
            
            try
            {
                // 检查请求是否已被取消
                if (HttpContext.RequestAborted.IsCancellationRequested)
                {
                    return StatusCode(408, new { error = "Request already cancelled", requestId = requestId });
                }
                
                // 将秒数转换为总毫秒数,分段延迟以检查取消
                int totalMilliseconds = seconds * 1000;
                int interval = 1000; // 每秒检查一次
                
                for (int i = 0; i < seconds; i++)
                {
                    // 检查请求是否已被取消
                    if (HttpContext.RequestAborted.IsCancellationRequested)
                    {
                        var cancelledResult = StatusCode(408, new { error = "--cancelled", secondsCompleted = i, requestId = requestId });
                        
                        // 发送SignalR消息,但要处理可能的连接问题
                        try
                        {
                            await _hubContext.Clients.Group($"request-{requestId}").SendAsync("ReceiveStatusUpdate", "cancelled", $"-- cancelled after {i} seconds", requestId);
                        }
                        catch (Exception ex)
                        {
                            // 记录错误但不抛出,因为HTTP响应已经发送
                            Console.WriteLine($"SignalR send failed: {ex.Message}");
                        }
                        
                        return cancelledResult;
                    }
                    
                    await Task.Delay(interval, HttpContext.RequestAborted);
                    
                    // Send periodic updates with request ID
                    try
                    {
                        await _hubContext.Clients.Group($"request-{requestId}").SendAsync("ReceiveStatusUpdate", "progress", $"Processing... {i+1}/{seconds} seconds completed", requestId);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"SignalR progress send failed: {ex.Message}");
                    }
                }
                
                // 检查请求是否已被取消
                if (HttpContext.RequestAborted.IsCancellationRequested)
                {
                    var cancelledResult = StatusCode(408, new { error = " cancelled ", requestId = requestId });
                    
                    // 发送SignalR消息,但要处理可能的连接问题
                    try
                    {
                        await _hubContext.Clients.Group($"request-{requestId}").SendAsync("ReceiveStatusUpdate", "cancelled", " -- cancelled  ", requestId);
                    }
                    catch (Exception ex)
                    {
                        // 记录错误但不抛出,因为HTTP响应已经发送
                        Console.WriteLine($"SignalR send failed: {ex.Message}");
                    }
                    
                    return cancelledResult;
                }
                
                var successResult = Ok(new { message = $" -- completed  for {seconds} seconds", timestamp = DateTime.Now, requestId = requestId });
                
                // 发送SignalR消息,但要处理可能的连接问题
                try
                {
                    await _hubContext.Clients.Group($"request-{requestId}").SendAsync("ReceiveStatusUpdate", "success", $" -- completed for {seconds} seconds", requestId);
                }
                catch (Exception ex)
                {
                    // 记录错误但不抛出,因为HTTP响应已经发送
                    Console.WriteLine($"SignalR send failed: {ex.Message}");
                }
                
                return successResult;
            }
            catch (OperationCanceledException)
            {
                var exceptionResult = StatusCode(408, new { error = "-- cancelled", requestId = requestId });
                
                // 发送SignalR消息,但要处理可能的连接问题
                try
                {
                    await _hubContext.Clients.Group($"request-{requestId}").SendAsync("ReceiveStatusUpdate", "cancelled", "--Task.Delay  cancelled", requestId);
                }
                catch (Exception ex)
                {
                    // 记录错误但不抛出,因为HTTP响应已经发送
                    Console.WriteLine($"SignalR send failed: {ex.Message}");
                }
                
                return exceptionResult;
            }
        }

好了,超时这块就先分析到这里,如果需要对应的测试代码可以联系我。 Q304418200

三、IIS 、Kestrive和AI 吐槽点

末了,吐槽一下分析原理和写测试代码时遇到的几个坑点

  1. IIS设置界面上的超时的超时设置都不是我们想要的接口运行超时时间。见文末附图。

  2. Kestrive 提供了对应IIS图上几个超时设置的方法,但是没有提供运行超时的设置属性(因为它想让我们通过 监听 HttpContext.RequestAborted 来实现 )。

  3. AI 一个劲的给出错误解释 和 错误代码, 先后试过 豆包、文心一言、DEEPSEEK、通义灵码包括微软自家的Copilot , 都给出了无效代码和 根本不存在的错误代码

cs 复制代码
//无效代码==>设置的不是运行时间超时
builder.WebHost.ConfigureKestrel(options =>
{
    options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(20);
    options.Limits.KeepAliveTimeout = TimeSpan.FromSeconds(20);
});

//错误代码==>根本不存在 DefautRequestTimeout  属性。
builder.WebHost.ConfigureKestrel(options =>
{
    options.DefautRequestTimeout = TimeSpan.FromSeconds(20); 
});

相关推荐
踏浪无痕2 小时前
JobFlow:固定分片如何解决分布式扫描的边界抖动
后端·面试·架构
q_19132846952 小时前
基于SpringBoot+Vue.js的高校竞赛活动信息平台
vue.js·spring boot·后端·mysql·程序员·计算机毕业设计
踏浪无痕3 小时前
JobFlow调度的难题:超时、补偿与漏调
后端·面试·架构
Postkarte不想说话3 小时前
ElasticSearch操作系统环境设置
后端
i听风逝夜3 小时前
Gradle秒级打包部署SpringBoot项目,行云流水
后端
why技术3 小时前
如果让我站在科技从业者的角度去回看 2025 年,让我选一个词出来形容它,我会选择“vibe coding”这个词。
前端·后端·程序员
喵个咪3 小时前
Go单协程事件调度器:游戏后端的无锁有序与响应时间掌控
后端·游戏开发
Kiyra4 小时前
八股篇(1):LocalThread、CAS和AQS
java·开发语言·spring boot·后端·中间件·性能优化·rocketmq
木风小助理4 小时前
在 Spring Boot 中实现 JSON 字段的蛇形命
spring boot·后端·json