.NET Framework版SignalR自托管Persistent Connection发送数据时内存泄漏问题排查求助
我来帮你分析下这个自托管SignalR的内存泄漏问题——从你提供的代码和现象来看,问题确实聚焦在Connection.Send这个调用上,结合.NET Framework版SignalR的特性,我整理几个排查方向和解决方案:
1. 先修正自托管WebApp的生命周期问题
你的启动代码里用了using (WebApp.Start<Startup>(url)) {},这个using块会在代码执行完后立即释放WebApp实例,而SignalR的内部连接管理依赖WebApp的生命周期来做资源清理。这种情况下,后续的Connection.Send操作可能会在一个已经处于异常状态的上下文里执行,导致资源无法正常回收。
修正方案:把WebApp实例保存为全局变量,在服务关闭时再手动释放:
private static IDisposable _selfHostedWebApp; // 启动自托管服务的方法 public void StartSelfHost() { var url = "http://*:8889"; _selfHostedWebApp = WebApp.Start<Startup>(url); } // 关闭服务时调用 public void StopSelfHost() { _selfHostedWebApp?.Dispose(); }
2. 改用ConnectionManager获取连接上下文发送消息
在PersistentConnection子类中直接使用基类的Connection属性,可能会导致上下文引用被意外持有,尤其是在自托管环境下。正确的做法是通过GlobalHost.ConnectionManager来获取连接上下文,这样能确保资源被正确管理:
修改HandleResult方法:
private void HandleResult(HeaderPacket response) { var data = JsonConvert.SerializeObject(response); // 获取当前连接类型的上下文 var connectionContext = GlobalHost.ConnectionManager.GetConnectionContext<ServerEndPoint>(); // 异步发送(建议用await避免同步阻塞导致的资源积压) _ = connectionContext.Connection.Send(response.ClientConnectionId, data); }
3. 排查JSON序列化的内存优化点
虽然你注释掉发送代码后泄漏消失,但序列化过程产生的字符串如果被SignalR内部缓存或持有,也可能加剧内存问题。可以尝试优化序列化方式:
- 使用池化的
StringBuilder来减少字符串分配:var sb = new StringBuilder(); using (var writer = new StringWriter(sb)) using (var jsonWriter = new JsonTextWriter(writer)) { var serializer = new JsonSerializer(); serializer.Serialize(jsonWriter, response); var data = sb.ToString(); // 发送data... }
4. 启用SignalR日志与内存分析定位泄漏对象
开启SignalR详细日志
在Startup.Configuration里添加日志配置,看看Send操作内部有没有异常或未释放的资源:
public void Configuration(IAppBuilder app) { // 开启全级别日志 GlobalHost.TraceManager.Switch.Level = SourceLevels.All; // 输出到控制台(也可以输出到文件) GlobalHost.TraceManager.Register(new TextWriterTraceListener(Console.Out)); GlobalHost.Configuration.MaxIncomingWebSocketMessageSize = int.MaxValue; app.MapSignalR(); app.MapSignalR<ServerEndPoint>("/Server"); }
使用内存诊断工具
用Visual Studio的内存诊断工具(Memory Profiler)抓取两次Send操作前后的内存快照,对比分析:
- 看哪种对象在持续累积(比如
Message、WebSocketBuffer、序列化后的字符串等) - 检查这些对象的引用链,看是被SignalR的哪个内部组件持有(比如消息缓冲区、连接上下文缓存等)
5. 调整SignalR的资源回收配置
在Startup里调整一些参数,帮助SignalR更快回收闲置资源:
public void Configuration(IAppBuilder app) { var config = GlobalHost.Configuration; // 减少消息缓冲区大小,避免积压过多未处理消息 config.DefaultMessageBufferSize = 50; // 缩短断开连接超时时间 config.DisconnectTimeout = TimeSpan.FromSeconds(20); // 调整心跳间隔,更快检测无效连接 config.KeepAlive = TimeSpan.FromSeconds(8); GlobalHost.Configuration.MaxIncomingWebSocketMessageSize = int.MaxValue; app.MapSignalR(); app.MapSignalR<ServerEndPoint>("/Server"); }
总结
优先尝试前两个方案(修正WebApp生命周期+改用ConnectionManager发送),这两个是自托管SignalR最常见的内存泄漏诱因。如果问题还存在,再通过内存分析工具定位具体的泄漏对象,针对性解决。
内容的提问来源于stack exchange,提问作者RWJ

