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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 04:05:11