如何避免蓝牙设备交互类中的时间耦合问题?
解决蓝牙设备交互类的API时间耦合问题
问题背景
我设计了一个用于蓝牙设备交互的Device类,但当前API存在时间耦合问题:客户端必须严格先调用ConnectAsync,才能调用SendMessageAsync;同时因为需要提前建立连接接收MessageReceived事件,无法在SendMessageAsync里延迟执行连接逻辑。现有代码如下:
class Device { private bool _isConnected; private readonly ulong _bluetoothAddress; public event EventHandler<Message> MessageReceived; public Device(ulong bluetoothAddress) { _bluetoothAddress = bluetoothAddress; } public async Task ConnectAsync() { // 异步连接逻辑... _isConnected = true; } public async Task SendMessageAsync(Message message) { if (!_isConnected) { throw new InvalidOperationException("Not connected"); } // 发送逻辑... } }
方案1:工厂方法创建已连接实例
将连接逻辑封装到静态工厂方法中,强制客户端只能拿到已完成连接、初始化好事件监听的Device实例,从根源上消除顺序依赖:
class Device { private bool _isConnected; private readonly ulong _bluetoothAddress; public event EventHandler<Message> MessageReceived; // 私有构造函数,禁止直接实例化 private Device(ulong bluetoothAddress) { _bluetoothAddress = bluetoothAddress; } // 静态工厂方法,返回已连接的Device实例 public static async Task<Device> CreateConnectedDeviceAsync(ulong bluetoothAddress) { var device = new Device(bluetoothAddress); await device.ConnectAndSetupListeningAsync(); return device; } // 内部执行连接+事件初始化逻辑 private async Task ConnectAndSetupListeningAsync() { // 异步连接蓝牙设备的逻辑... // 初始化消息接收监听,确保连接后能触发MessageReceived事件 RegisterMessageListener(); _isConnected = true; } private void RegisterMessageListener() { // 在这里完成蓝牙消息接收的回调注册 } public async Task SendMessageAsync(Message message) { // 无需再检查连接状态,实例一定是已连接的 // 发送消息逻辑... } }
客户端调用示例:
var device = await Device.CreateConnectedDeviceAsync(bluetoothAddr); device.MessageReceived += (sender, msg) => { /* 处理收到的消息 */ }; await device.SendMessageAsync(new Message("test"));
方案2:内部自动维护连接状态
在类内部实现懒加载连接逻辑,不管客户端先调用ConnectAsync还是直接调用SendMessageAsync,都自动完成连接和事件初始化,同时用锁处理并发场景:
class Device { private bool _isConnected; private readonly ulong _bluetoothAddress; private readonly SemaphoreSlim _connectionLock = new SemaphoreSlim(1, 1); public event EventHandler<Message> MessageReceived; public Device(ulong bluetoothAddress) { _bluetoothAddress = bluetoothAddress; } // 内部确保连接完成的方法 private async Task EnsureConnectedAsync() { if (_isConnected) return; await _connectionLock.WaitAsync(); try { if (_isConnected) return; // 双重检查,避免并发重复连接 // 异步连接逻辑... // 初始化消息监听 RegisterMessageListener(); _isConnected = true; } finally { _connectionLock.Release(); } } private void RegisterMessageListener() { // 注册消息接收回调 } // 保留主动连接方法,供客户端提前触发连接 public async Task ConnectAsync() { await EnsureConnectedAsync(); } public async Task SendMessageAsync(Message message) { await EnsureConnectedAsync(); // 发送消息逻辑... } }
这个方案既保留了客户端主动提前连接的能力,又允许直接调用SendMessageAsync自动完成连接,同时避免了并发场景下的重复连接问题。
方案3:分离设备元数据与连接会话
把"设备信息"和"已连接交互"拆分为两个类,用Device存储蓝牙地址等元数据,DeviceSession专门处理已连接状态下的消息收发和事件监听:
// 仅存储设备元数据的类 class Device { public ulong BluetoothAddress { get; } public Device(ulong bluetoothAddress) { BluetoothAddress = bluetoothAddress; } } // 已连接的会话类,负责交互逻辑 class DeviceSession { private readonly Device _device; public event EventHandler<Message> MessageReceived; private DeviceSession(Device device) { _device = device; } public static async Task<DeviceSession> CreateAsync(Device device) { var session = new DeviceSession(device); await session.ConnectAndSetupListening(); return session; } private async Task ConnectAndSetupListening() { // 连接蓝牙设备逻辑... // 初始化消息监听逻辑... } public async Task SendMessageAsync(Message message) { // 发送消息逻辑... } }
客户端调用示例:
var device = new Device(bluetoothAddr); var session = await DeviceSession.CreateAsync(device); session.MessageReceived += (sender, msg) => { /* 处理消息 */ }; await session.SendMessageAsync(new Message("test"));
这种模式语义更清晰,明确区分"设备存在"和"设备已连接"两个状态,不会出现未连接的会话实例。
内容的提问来源于stack exchange,提问作者Codisattva
相关产品推荐
相关产品推荐

