Skip to content

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事件IFBroSharpDOMWssClientnew FBroSharpDOMWssClient(websocket)保存连接以便后续通信
浏览器事件IFBroSharpBrowsernew FBroSharpBrowser(browser)在类中保存浏览器引用
框架事件IFBroSharpFramenew FBroSharpFrame(frame)保存特定框架引用
请求事件IFBroSharpRequestnew FBroSharpRequest(request)异步处理HTTP请求
响应事件IFBroSharpResponsenew 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("=====================");
    }
}

总结

核心要点

  1. 理解作用域限制:事件参数只在方法执行期间有效
  2. 使用强拷贝:通过构造函数创建独立对象副本
  3. 及时清理:在适当时机设置为null并调用Dispose
  4. 检查有效性:使用前always调用IsValid()
  5. 异常处理:包装所有操作以防止意外错误

最佳实践清单

  • [ ] 使用具体类型而不是接口声明变量
  • [ ] 使用拷贝构造函数创建强拷贝
  • [ ] 在对象使用前检查IsValid()
  • [ ] 在连接关闭时及时清理资源
  • [ ] 使用try-catch包装所有FBro对象操作
  • [ ] 考虑使用定时器清理无效对象
  • [ ] 在多线程环境中使用锁保护共享对象
  • [ ] 记录详细日志用于调试

这种设计虽然增加了一些复杂性,但极大地提高了FBro应用程序的内存效率和稳定性,特别适合需要处理大量事件和长期运行的应用场景。

如果文档对您有帮助,欢迎 请喝咖啡 ☕ | 软件发布 | 源码购买