ASP.NET Core Web API服务流读取挂起,如何检测网络异常?
解决ASP.NET Core断点续传服务端网络中断检测问题
问题背景
我正在修改一个支持断点续传文件上传的ASP.NET Core Web API服务(托管于Windows Service),该服务在多数故障场景下都能正常恢复上传,但存在如下问题。
问题现象
当服务部署在另一台计算机,客户端在本地且断开网线时,客户端能检测到网络中断,但服务会挂起在fileSection.FileStream.Read()处。有时服务会在8分钟、20分钟后检测到故障,有时则永远无法检测到。此外我发现,断开网线并停止客户端后,服务卡在Read()函数,此时文件大小为x KB,但当服务最终检测到异常时,会额外写入4 KB数据,这不符合预期,因为我已关闭缓冲且缓冲区大小为2 KB。
核心解决方案
1. 给服务端Stream读取添加主动超时+取消令牌
你当前用的同步Read()方法无法响应取消信号,很容易导致挂起。建议改用异步读取+组合取消令牌的方式,同时设置业务层面的超时,主动中断无响应的读取操作:
public static async Task<List<(Guid, string)>> StreamFileAsync( this HttpRequest request, DeviceId deviceId, FileTransferInfo transferInfo) { var boundary = GetBoundary(MediaTypeHeaderValue.Parse(request.ContentType), DefaultFormOptions.MultipartBoundaryLengthLimit); var reader = new MultipartReader(boundary, request.Body); // 创建带2分钟超时的取消令牌源,可根据业务调整时长 using var timeoutCts = new CancellationTokenSource(TimeSpan.FromMinutes(2)); // 合并请求自带的取消令牌和超时令牌,兼顾外部取消和超时场景 var combinedToken = CancellationTokenSource.CreateLinkedTokenSource(_cancellationToken, timeoutCts.Token).Token; var section = await reader.ReadNextSectionAsync(combinedToken); if (section != null) { var fileSection = section.AsFileSection(); var targetPath = transferInfo.FileTempPath; try { using (var outfile = new FileStream(targetPath, FileMode.Append, FileAccess.Write, FileShare.None)) { var buffer = new byte[DefaultCopyBufferSize]; int read; // 改用异步ReadAsync并传入取消令牌,超时或外部取消时会抛出OperationCanceledException while ((read = await fileSection.FileStream.ReadAsync(buffer, 0, buffer.Length, combinedToken)) > 0) { await outfile.WriteAsync(buffer, 0, read, combinedToken); transferInfo.BytesSaved += read; } } } catch (OperationCanceledException) { // 处理超时/主动取消的情况:比如标记上传中断、清理无效临时数据 } catch (Exception e) { // 处理其他IO/网络异常 } } }
2. 优化Kestrel服务器的连接超时配置
你之前只设置了KeepAliveTimeout,还需要补充两个关键参数,让Kestrel能更快检测到空闲/异常连接:
var host = new WebHostBuilder() .UseKestrel(o => { o.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2); o.Limits.RequestHeadersTimeout = TimeSpan.FromMinutes(1); // 限制等待请求头的最长时间 o.Limits.IdleTimeout = TimeSpan.FromMinutes(2); // 空闲连接自动断开时长 }) // 其他宿主配置...
3. 修复额外写入4KB数据的问题
这个问题是底层网络缓冲区的残留数据导致的——连接中断后,操作系统会把缓冲区里的剩余数据推给服务端。可以在异常捕获后,根据transferInfo.BytesSaved截断文件,保证文件大小和实际上传的字节数一致:
catch (Exception e) { // 截断文件到实际已保存的字节数 using var truncateStream = new FileStream(targetPath, FileMode.Open, FileAccess.Write); truncateStream.SetLength(transferInfo.BytesSaved); // 其他异常处理逻辑 }
4. 客户端侧的辅助优化(可选)
虽然问题核心在服务端,但客户端做些调整能更顺畅地配合中断检测:
- 把老旧的
HttpWebRequest换成HttpClient,它对取消令牌和超时的支持更完善 - 客户端检测到断网时,主动调用取消令牌的
Cancel()方法终止上传请求,避免服务端长时间等待
关键原理说明
- 同步
Read()方法不会响应取消令牌,必须用异步IO方法结合CancellationToken才能主动中断挂起操作 - Kestrel的超时参数控制连接层面的空闲状态,而业务代码的超时是针对读取操作的精准控制
- 额外写入的字节是网络缓冲区的残留,通过截断文件可以修正文件大小的异常
内容的提问来源于stack exchange,提问作者B G
相关产品推荐
相关产品推荐

