.NET 5 SSE场景下Response抛出ObjectDisposedException问题求助
解决SSE中
System.ObjectDisposedException: IFeatureCollection has been disposed异常 核心问题分析
这个异常通常是因为异步操作脱离了ASP.NET请求上下文的跟踪,或者客户端断开连接后,代码仍在尝试写入已被释放的Response流。你的代码存在几个关键问题:
- 使用
async void方法:ASP.NET无法跟踪async void的异步完成状态,会提前释放请求上下文。 - 自定义循环未绑定请求取消信号:客户端断开时请求上下文已释放,但
IsSubscriptionAlive循环还在继续执行写入操作。 - 写入流时忽略取消令牌:没有响应客户端断开的信号,导致尝试写入已释放的资源。
具体修复方案
1. 将async void改为async Task
ASP.NET Core中,控制器异步方法必须返回Task/Task<T>,框架才能正确跟踪异步生命周期,避免提前释放上下文。
2. 绑定请求取消令牌
使用HttpContext.RequestAborted作为循环和异步操作的取消信号,客户端断开连接时会自动触发这个令牌,及时终止操作。
3. 写入前检查响应状态
在写入Response流前,先检查HttpContext.Response.Body.CanWrite和!HttpContext.RequestAborted.IsCancellationRequested,确保响应仍处于有效状态。
4. 传递取消令牌到所有异步操作
将RequestAborted传递给FlushAsync、FetchNewStrikesAsync等方法,确保异步操作能响应取消信号。
修改后的代码示例
public async Task SubscribeAsync([BindRequired] string uid, [BindRequired] int tableSource, [FromQuery, BindRequired] List<int> areaIds, int? type = null) { var allowedAreas = Response.Headers["allowedAreas"].ToString(); var filteredAreas = areaIds.Where(x => allowedAreas.Contains(x.ToString())).ToList(); HttpContext.Response.Headers.Add("Content-Type", "text/event-stream"); HttpContext.Response.Headers.Add("Access-Control-Allow-Origin", "*"); var cancellationToken = HttpContext.RequestAborted; StartKeepAlive(uid, cancellationToken); try { UnsubscribeAsync(uid); if (IsSubscriptionAllowed(uid)) { PushLastStrikes(tableSource, filteredAreas, type); while (!cancellationToken.IsCancellationRequested && IsSubscriptionAlive(uid)) { var results = (await _strikeWarningRepository.FetchNewStrikesAsync(tableSource, filteredAreas, type, cancellationToken)).ToList(); _logger.LogInformation("Feeding strikes to the user."); if (results.Count == 0 || !HttpContext.Response.Body.CanWrite) continue; var buffer = 0; do { if (cancellationToken.IsCancellationRequested || !HttpContext.Response.Body.CanWrite) break; var bufferedResults = results.Skip(buffer).Take(100).ToList(); var dataBytes = Encoding.UTF8.GetBytes($"data: {JsonSerializer.Serialize(bufferedResults)}\n\n"); await Response.BodyWriter.WriteAsync(dataBytes, cancellationToken); await Response.BodyWriter.FlushAsync(cancellationToken); buffer += 100; } while (buffer < results.Count && !cancellationToken.IsCancellationRequested); } } } catch (ObjectDisposedException) { _logger.LogInformation("Client disconnected, subscription ended for uid: {Uid}", uid); } catch (Exception ex) { _logger.LogCritical(ex, "Subscription failed for uid: {Uid}", uid); } finally { UnsubscribeAsync(uid); } }
额外优化建议
- KeepAlive绑定取消令牌:修改
StartKeepAlive方法,接收CancellationToken,令牌触发时立即停止心跳,避免连接断开后仍尝试写入流。 - 异步化辅助方法:如果
IsSubscriptionAlive、UnsubscribeAsync涉及IO或锁操作,改为异步方法,避免阻塞异步流程。 - 批量写入优化:预先序列化所有数据再分批写入,减少序列化次数,注意控制内存占用。
内容的提问来源于stack exchange,提问作者Fernando Calazans
相关产品推荐
相关产品推荐

