FBro事件类传递机制说明
概述
FBro框架在新版本中采用了一种特殊的事件类传递机制,事件回调方法中的接口参数只在该方法的作用域内有效。这种设计旨在降低内存占用、加快C++对象释放,避免无谓的资源占用,无需等待.NET GC回收即可快速回收底层资源。
核心概念
作用域限制原理
在FBro事件系统中,所有传递给事件回调方法的接口参数都有严格的作用域限制:
csharp
public override void VIP_OnWebSocketClientConnect(IFBroSharpBrowser browser, IFBroSharpFrame frame,
IFBroSharpDOMWssClient websocket, ref string returl, ref string protocols)
{
// ========== 作用域开始 ==========
// websocket、browser、frame参数在这里有效
// 这些参数指向C++底层的临时对象
Console.WriteLine("WebSocket连接建立");
// ========== 方法执行完毕,作用域结束 ==========
// 所有参数对象被C++底层销毁
// 如果在类中保存了这些参数的直接引用,将变为无效
}内存管理模型
csharp
// FBro的跨语言内存管理:
// C++底层对象 ←→ C#包装对象
// 事件参数的生命周期:
// 1. C++创建临时对象
// 2. C#获得包装引用(仅在方法作用域内有效)
// 3. 方法执行完毕
// 4. C++立即销毁临时对象
// 5. C#包装引用失效错误用法:弱拷贝
错误示例
csharp
public class WebSocketManager
{
private IFBroSharpDOMWssClient wss; // 类成员变量
public override void VIP_OnWebSocketClientConnect(IFBroSharpBrowser browser, IFBroSharpFrame frame,
IFBroSharpDOMWssClient websocket, ref string returl, ref string protocols)
{
// ❌ 错误方式:直接赋值(弱拷贝)
wss = websocket;
Console.WriteLine("WebSocket已保存"); // 这里看起来正常
}
public void SendMessage(string message)
{
// ❌ 这里wss很可能已经无效了
if (wss != null)
{
// 即使wss不为null,IsValid()也会返回false
if (wss.IsValid())
{
wss.SendText(message); // 这行代码永远不会执行
}
else
{
Console.WriteLine("WebSocket对象已失效"); // 总是输出这个
}
}
}
}弱拷贝的问题
| 问题 | 说明 | 后果 |
|---|---|---|
| 引用失效 | 方法结束后底层对象被销毁 | IsValid()返回false |
| 操作失败 | 对失效对象的任何操作都无效 | 方法调用无响应或抛异常 |
| 内存泄漏风险 | C#对象持有无效引用 | 可能影响GC回收效率 |
| 调试困难 | 问题不会立即显现 | 在后续使用时才发现无效 |
正确用法:强拷贝
正确示例
csharp
public class WebSocketManager
{
private FBroSharpDOMWssClient wss; // 注意:使用具体类而不是接口
public override void VIP_OnWebSocketClientConnect(IFBroSharpBrowser browser, IFBroSharpFrame frame,
IFBroSharpDOMWssClient websocket, ref string returl, ref string protocols)
{
try
{
// ✅ 正确方式:创建新实例(强拷贝)
wss = new FBroSharpDOMWssClient(websocket);
// 这样创建了一个独立的对象副本
// 即使原始的websocket参数被销毁,wss仍然有效
Console.WriteLine("WebSocket连接已成功保存");
}
catch (Exception ex)
{
Console.WriteLine($"保存WebSocket连接失败: {ex.Message}");
}
}
public void SendMessage(string message)
{
try
{
// ✅ 这里wss是有效的独立对象
if (wss != null && wss.IsValid())
{
wss.SendText(message); // 正常工作
Console.WriteLine($"消息已发送: {message}");
}
else
{
Console.WriteLine("WebSocket连接无效或已关闭");
}
}
catch (Exception ex)
{
Console.WriteLine($"发送消息失败: {ex.Message}");
}
}
public override void VIP_OnWebSocketClientClose(IFBroSharpBrowser browser, IFBroSharpFrame frame,
IFBroSharpDOMWssClient websocket)
{
// ✅ 及时清理,帮助GC回收
if (wss != null)
{
try
{
// 如果对象有Dispose方法,先调用
wss.Dispose();
}
catch (Exception ex)
{
Console.WriteLine($"清理WebSocket连接时出错: {ex.Message}");
}
finally
{
wss = null; // 设置为null,帮助GC回收
}
}
Console.WriteLine("WebSocket连接已清理");
}
}强拷贝的关键点
| 要点 | 说明 | 示例 |
|---|---|---|
| 使用具体类 | 声明变量时使用具体类而不是接口 | FBroSharpDOMWssClient wss |
| 构造函数拷贝 | 使用拷贝构造函数创建新实例 | new FBroSharpDOMWssClient(websocket) |
| 独立生命周期 | 新对象有独立的生命周期 | 不受原始参数作用域限制 |
| 及时清理 | 在适当时机设置为null | 连接关闭时清理引用 |
完整的最佳实践
高级WebSocket连接管理器
csharp
/// <summary>
/// WebSocket连接管理器 - 演示正确的事件类传递处理
/// </summary>
public class AdvancedWebSocketManager
{
private readonly Dictionary<string, WebSocketConnection> _connections = new Dictionary<string, WebSocketConnection>();
private readonly object _lock = new object();
private readonly Timer _cleanupTimer;
public AdvancedWebSocketManager()
{
// 定期清理无效连接
_cleanupTimer = new Timer(CleanupInvalidConnections, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
}
/// <summary>
/// WebSocket连接建立事件
/// </summary>
public override void VIP_OnWebSocketClientConnect(IFBroSharpBrowser browser, IFBroSharpFrame frame,
IFBroSharpDOMWssClient websocket, ref string returl, ref string protocols)
{
try
{
// ✅ 创建强拷贝 - 关键步骤
var connectionId = Guid.NewGuid().ToString();
var wsConnection = new FBroSharpDOMWssClient(websocket);
var browserCopy = new FBroSharpBrowser(browser); // 如果需要保存browser
var frameCopy = new FBroSharpFrame(frame); // 如果需要保存frame
var connection = new WebSocketConnection
{
Id = connectionId,
WebSocket = wsConnection,
Browser = browserCopy,
Frame = frameCopy,
ConnectedTime = DateTime.Now,
LastActivity = DateTime.Now
};
lock (_lock)
{
_connections[connectionId] = connection;
}
Console.WriteLine($"WebSocket连接已建立: {connectionId}");
Console.WriteLine($"连接地址: {returl}");
Console.WriteLine($"协议: {protocols}");
Console.WriteLine($"当前连接数: {_connections.Count}");
// 发送欢迎消息
SendWelcomeMessage(connectionId);
}
catch (Exception ex)
{
Console.WriteLine($"处理WebSocket连接失败: {ex.Message}");
}
}
/// <summary>
/// WebSocket消息接收事件
/// </summary>
public override void VIP_OnWebSocketClientMessage(IFBroSharpBrowser browser, IFBroSharpFrame frame,
IFBroSharpDOMWssClient websocket, string message)
{
try
{
// 注意:这里的websocket参数也是临时的
// 如果需要在异步操作中使用,也需要创建副本
// 找到对应的连接
WebSocketConnection connection = null;
lock (_lock)
{
connection = _connections.Values.FirstOrDefault(c =>
c.WebSocket != null && c.WebSocket.IsValid() &&
AreWebSocketsEqual(c.WebSocket, websocket));
}
if (connection != null)
{
connection.LastActivity = DateTime.Now;
Console.WriteLine($"收到消息 [{connection.Id}]: {message}");
// 处理消息(可以在这里添加业务逻辑)
ProcessMessage(connection.Id, message);
}
else
{
Console.WriteLine("收到来自未知连接的消息");
}
}
catch (Exception ex)
{
Console.WriteLine($"处理WebSocket消息失败: {ex.Message}");
}
}
/// <summary>
/// WebSocket连接关闭事件
/// </summary>
public override void VIP_OnWebSocketClientClose(IFBroSharpBrowser browser, IFBroSharpFrame frame,
IFBroSharpDOMWssClient websocket)
{
try
{
// 找到并移除对应的连接
string connectionId = null;
lock (_lock)
{
var connectionToRemove = _connections.FirstOrDefault(kvp =>
kvp.Value.WebSocket != null &&
AreWebSocketsEqual(kvp.Value.WebSocket, websocket));
if (connectionToRemove.Key != null)
{
connectionId = connectionToRemove.Key;
// ✅ 清理资源
var connection = connectionToRemove.Value;
connection.WebSocket?.Dispose();
connection.Browser?.Dispose();
connection.Frame?.Dispose();
// 设置为null帮助GC
connection.WebSocket = null;
connection.Browser = null;
connection.Frame = null;
_connections.Remove(connectionId);
}
}
if (connectionId != null)
{
Console.WriteLine($"WebSocket连接已关闭: {connectionId}");
Console.WriteLine($"当前连接数: {_connections.Count}");
}
else
{
Console.WriteLine("WebSocket连接关闭,但未找到对应的连接记录");
}
}
catch (Exception ex)
{
Console.WriteLine($"处理WebSocket连接关闭失败: {ex.Message}");
}
}
/// <summary>
/// 向指定连接发送消息
/// </summary>
public bool SendMessage(string connectionId, string message)
{
try
{
WebSocketConnection connection = null;
lock (_lock)
{
_connections.TryGetValue(connectionId, out connection);
}
if (connection?.WebSocket != null && connection.WebSocket.IsValid())
{
connection.WebSocket.SendText(message);
connection.LastActivity = DateTime.Now;
Console.WriteLine($"消息已发送到 [{connectionId}]: {message}");
return true;
}
else
{
Console.WriteLine($"连接 [{connectionId}] 无效或已关闭");
return false;
}
}
catch (Exception ex)
{
Console.WriteLine($"发送消息到 [{connectionId}] 失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 广播消息到所有连接
/// </summary>
public int BroadcastMessage(string message)
{
int successCount = 0;
var invalidConnections = new List<string>();
lock (_lock)
{
foreach (var kvp in _connections)
{
try
{
if (kvp.Value.WebSocket != null && kvp.Value.WebSocket.IsValid())
{
kvp.Value.WebSocket.SendText(message);
kvp.Value.LastActivity = DateTime.Now;
successCount++;
}
else
{
invalidConnections.Add(kvp.Key);
}
}
catch (Exception ex)
{
Console.WriteLine($"广播到 [{kvp.Key}] 失败: {ex.Message}");
invalidConnections.Add(kvp.Key);
}
}
// 清理无效连接
foreach (var connectionId in invalidConnections)
{
CleanupConnection(connectionId);
}
}
Console.WriteLine($"广播消息完成,成功: {successCount}, 清理无效连接: {invalidConnections.Count}");
return successCount;
}
/// <summary>
/// 获取连接统计信息
/// </summary>
public ConnectionStatistics GetStatistics()
{
lock (_lock)
{
var validConnections = _connections.Values.Where(c => c.WebSocket?.IsValid() == true).ToList();
return new ConnectionStatistics
{
TotalConnections = _connections.Count,
ValidConnections = validConnections.Count,
InvalidConnections = _connections.Count - validConnections.Count,
OldestConnection = validConnections.Count > 0 ? validConnections.Min(c => c.ConnectedTime) : (DateTime?)null,
NewestConnection = validConnections.Count > 0 ? validConnections.Max(c => c.ConnectedTime) : (DateTime?)null,
LastActivity = validConnections.Count > 0 ? validConnections.Max(c => c.LastActivity) : (DateTime?)null
};
}
}
/// <summary>
/// 清理所有连接
/// </summary>
public void Cleanup()
{
lock (_lock)
{
foreach (var kvp in _connections)
{
CleanupConnection(kvp.Value);
}
_connections.Clear();
}
_cleanupTimer?.Dispose();
Console.WriteLine("WebSocket管理器已清理");
// 强制GC(特殊情况下可以考虑)
GC.Collect();
GC.WaitForPendingFinalizers();
}
#region 私有方法
/// <summary>
/// 发送欢迎消息
/// </summary>
private void SendWelcomeMessage(string connectionId)
{
var welcomeMessage = new
{
type = "welcome",
connectionId = connectionId,
timestamp = DateTime.Now,
message = "欢迎连接到FBro WebSocket服务器"
};
SendMessage(connectionId, JsonConvert.SerializeObject(welcomeMessage));
}
/// <summary>
/// 处理接收到的消息
/// </summary>
private void ProcessMessage(string connectionId, string message)
{
try
{
// 这里可以添加业务逻辑
// 例如:解析JSON、路由到不同处理器等
var response = new
{
type = "echo",
originalMessage = message,
timestamp = DateTime.Now,
connectionId = connectionId
};
SendMessage(connectionId, JsonConvert.SerializeObject(response));
}
catch (Exception ex)
{
Console.WriteLine($"处理消息失败: {ex.Message}");
}
}
/// <summary>
/// 比较两个WebSocket对象是否相等
/// </summary>
private bool AreWebSocketsEqual(IFBroSharpDOMWssClient ws1, IFBroSharpDOMWssClient ws2)
{
// 这里需要根据实际的FBro API来实现比较逻辑
// 可能需要比较ID、句柄或其他唯一标识符
try
{
return ws1.GetHashCode() == ws2.GetHashCode();
}
catch
{
return false;
}
}
/// <summary>
/// 清理指定连接
/// </summary>
private void CleanupConnection(string connectionId)
{
if (_connections.TryGetValue(connectionId, out var connection))
{
CleanupConnection(connection);
_connections.Remove(connectionId);
}
}
/// <summary>
/// 清理连接对象
/// </summary>
private void CleanupConnection(WebSocketConnection connection)
{
try
{
connection.WebSocket?.Dispose();
connection.Browser?.Dispose();
connection.Frame?.Dispose();
}
catch (Exception ex)
{
Console.WriteLine($"清理连接资源时出错: {ex.Message}");
}
finally
{
connection.WebSocket = null;
connection.Browser = null;
connection.Frame = null;
}
}
/// <summary>
/// 定期清理无效连接
/// </summary>
private void CleanupInvalidConnections(object state)
{
try
{
var invalidConnections = new List<string>();
lock (_lock)
{
foreach (var kvp in _connections)
{
if (kvp.Value.WebSocket == null || !kvp.Value.WebSocket.IsValid())
{
invalidConnections.Add(kvp.Key);
}
}
foreach (var connectionId in invalidConnections)
{
CleanupConnection(connectionId);
}
}
if (invalidConnections.Count > 0)
{
Console.WriteLine($"定期清理完成,移除 {invalidConnections.Count} 个无效连接");
}
}
catch (Exception ex)
{
Console.WriteLine($"定期清理失败: {ex.Message}");
}
}
#endregion
}
/// <summary>
/// WebSocket连接信息
/// </summary>
public class WebSocketConnection
{
public string Id { get; set; }
public FBroSharpDOMWssClient WebSocket { get; set; }
public FBroSharpBrowser Browser { get; set; }
public FBroSharpFrame Frame { get; set; }
public DateTime ConnectedTime { get; set; }
public DateTime LastActivity { get; set; }
}
/// <summary>
/// 连接统计信息
/// </summary>
public class ConnectionStatistics
{
public int TotalConnections { get; set; }
public int ValidConnections { get; set; }
public int InvalidConnections { get; set; }
public DateTime? OldestConnection { get; set; }
public DateTime? NewestConnection { get; set; }
public DateTime? LastActivity { get; set; }
}适用的FBro事件类型
需要强拷贝的常见事件参数
| 事件类型 | 参数类型 | 强拷贝方式 | 使用场景 |
|---|---|---|---|
| WebSocket事件 | IFBroSharpDOMWssClient | new FBroSharpDOMWssClient(websocket) | 保存连接以便后续通信 |
| 浏览器事件 | IFBroSharpBrowser | new FBroSharpBrowser(browser) | 在类中保存浏览器引用 |
| 框架事件 | IFBroSharpFrame | new FBroSharpFrame(frame) | 保存特定框架引用 |
| 请求事件 | IFBroSharpRequest | new FBroSharpRequest(request) | 异步处理HTTP请求 |
| 响应事件 | IFBroSharpResponse | new FBroSharpResponse(response) | 延迟响应处理 |
事件处理模式
csharp
/// <summary>
/// 通用事件处理模式模板
/// </summary>
public abstract class FBroEventHandlerBase
{
/// <summary>
/// 处理需要保存的事件参数
/// </summary>
protected T SaveEventParameter<T>(object parameter) where T : class
{
try
{
// 使用反射创建强拷贝
var constructor = typeof(T).GetConstructor(new[] { parameter.GetType() });
if (constructor != null)
{
return (T)constructor.Invoke(new[] { parameter });
}
// 如果没有拷贝构造函数,记录警告
Console.WriteLine($"警告:{typeof(T).Name} 没有拷贝构造函数,无法创建强拷贝");
return null;
}
catch (Exception ex)
{
Console.WriteLine($"创建 {typeof(T).Name} 强拷贝失败: {ex.Message}");
return null;
}
}
/// <summary>
/// 安全清理对象
/// </summary>
protected void SafeCleanup<T>(ref T obj) where T : class, IDisposable
{
try
{
obj?.Dispose();
}
catch (Exception ex)
{
Console.WriteLine($"清理 {typeof(T).Name} 对象失败: {ex.Message}");
}
finally
{
obj = null;
}
}
}设计原理和优势
1. 内存效率
csharp
// 传统方式的问题:
// - 事件参数在整个应用生命周期内保持活跃
// - C++对象无法及时释放
// - 增加跨语言内存管理复杂性
// FBro方式的优势:
// - 事件参数仅在需要时存在
// - C++对象可以立即释放
// - 减少内存占用和GC压力2. 性能优势
| 优势 | 说明 | 效果 |
|---|---|---|
| 快速释放 | C++对象无需等待.NET GC即可释放 | 减少内存占用 |
| 减少跨语言开销 | 减少C++和C#之间的对象生命周期管理 | 提高性能 |
| 强制最佳实践 | 迫使开发者明确对象生命周期 | 避免内存泄漏 |
| 可预测回收 | 提供更可预测的资源释放时机 | 稳定的内存使用 |
3. 资源管理优势
csharp
// 好处:
// 1. 强制开发者明确对象生命周期
// 2. 避免意外的长期引用导致内存泄漏
// 3. 提供更可预测的资源释放时机
// 4. 减少跨语言资源管理的复杂性
// 5. 提高应用程序的整体稳定性常见错误和解决方案
错误1:直接保存事件参数
csharp
// ❌ 错误
public class BadExample
{
private IFBroSharpDOMWssClient savedWebSocket;
public override void VIP_OnWebSocketClientConnect(IFBroSharpBrowser browser, IFBroSharpFrame frame,
IFBroSharpDOMWssClient websocket, ref string returl, ref string protocols)
{
savedWebSocket = websocket; // 弱拷贝,会失效
}
}
// ✅ 正确
public class GoodExample
{
private FBroSharpDOMWssClient savedWebSocket;
public override void VIP_OnWebSocketClientConnect(IFBroSharpBrowser browser, IFBroSharpFrame frame,
IFBroSharpDOMWssClient websocket, ref string returl, ref string protocols)
{
savedWebSocket = new FBroSharpDOMWssClient(websocket); // 强拷贝
}
}错误2:忘记检查对象有效性
csharp
// ❌ 错误
public void SendMessage(string message)
{
savedWebSocket.SendText(message); // 可能对象已失效
}
// ✅ 正确
public void SendMessage(string message)
{
if (savedWebSocket != null && savedWebSocket.IsValid())
{
savedWebSocket.SendText(message);
}
else
{
Console.WriteLine("WebSocket对象无效");
}
}错误3:忘记清理资源
csharp
// ❌ 错误
public override void VIP_OnWebSocketClientClose(IFBroSharpBrowser browser, IFBroSharpFrame frame,
IFBroSharpDOMWssClient websocket)
{
// 没有清理保存的对象
}
// ✅ 正确
public override void VIP_OnWebSocketClientClose(IFBroSharpBrowser browser, IFBroSharpFrame frame,
IFBroSharpDOMWssClient websocket)
{
if (savedWebSocket != null)
{
savedWebSocket.Dispose(); // 如果有Dispose方法
savedWebSocket = null; // 帮助GC回收
}
}调试和诊断
检查对象有效性
csharp
/// <summary>
/// 对象有效性检查工具
/// </summary>
public static class FBroObjectValidator
{
/// <summary>
/// 检查WebSocket对象是否有效
/// </summary>
public static bool IsValidWebSocket(IFBroSharpDOMWssClient websocket)
{
try
{
return websocket != null && websocket.IsValid();
}
catch (Exception ex)
{
Console.WriteLine($"检查WebSocket有效性失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 检查浏览器对象是否有效
/// </summary>
public static bool IsValidBrowser(IFBroSharpBrowser browser)
{
try
{
return browser != null && browser.IsValid();
}
catch (Exception ex)
{
Console.WriteLine($"检查Browser有效性失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 检查框架对象是否有效
/// </summary>
public static bool IsValidFrame(IFBroSharpFrame frame)
{
try
{
return frame != null && frame.IsValid();
}
catch (Exception ex)
{
Console.WriteLine($"检查Frame有效性失败: {ex.Message}");
return false;
}
}
}诊断工具
csharp
/// <summary>
/// FBro对象诊断工具
/// </summary>
public static class FBroDiagnostics
{
/// <summary>
/// 诊断对象状态
/// </summary>
public static void DiagnoseObject(object obj, string objectName)
{
Console.WriteLine($"=== {objectName} 诊断信息 ===");
if (obj == null)
{
Console.WriteLine("对象为 null");
return;
}
Console.WriteLine($"对象类型: {obj.GetType().FullName}");
// 检查是否有IsValid方法
var isValidMethod = obj.GetType().GetMethod("IsValid");
if (isValidMethod != null)
{
try
{
bool isValid = (bool)isValidMethod.Invoke(obj, null);
Console.WriteLine($"IsValid(): {isValid}");
}
catch (Exception ex)
{
Console.WriteLine($"调用IsValid()失败: {ex.Message}");
}
}
else
{
Console.WriteLine("对象没有IsValid()方法");
}
Console.WriteLine($"对象哈希码: {obj.GetHashCode()}");
Console.WriteLine("=====================");
}
}总结
核心要点
- 理解作用域限制:事件参数只在方法执行期间有效
- 使用强拷贝:通过构造函数创建独立对象副本
- 及时清理:在适当时机设置为null并调用Dispose
- 检查有效性:使用前always调用IsValid()
- 异常处理:包装所有操作以防止意外错误
最佳实践清单
- [ ] 使用具体类型而不是接口声明变量
- [ ] 使用拷贝构造函数创建强拷贝
- [ ] 在对象使用前检查IsValid()
- [ ] 在连接关闭时及时清理资源
- [ ] 使用try-catch包装所有FBro对象操作
- [ ] 考虑使用定时器清理无效对象
- [ ] 在多线程环境中使用锁保护共享对象
- [ ] 记录详细日志用于调试
这种设计虽然增加了一些复杂性,但极大地提高了FBro应用程序的内存效率和稳定性,特别适合需要处理大量事件和长期运行的应用场景。