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

.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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 16:54:17