基于 MQTT 的异步请求-响应模式

由于 MQTT 本质上是发布/订阅(Pub/Sub)模型,是单向的(发出去就不管了)。但业务场景(如删除人脸)需要知道设备是否执行成功,因此这段代码通过 TaskCompletionSource 模拟了类似 HTTP 的"同步等待"效果。

复制代码
private readonly ConcurrentDictionary<string, TaskCompletionSource<MqttDeviceResponse>> _responseWaiters = new();

这段代码实现了一个基于 MQTT 的异步请求-响应(Request-Response)模式

由于 MQTT 本质上是发布/订阅(Pub/Sub)模型,是单向的(发出去就不管了)。但你的业务场景(如删除人脸)需要知道设备是否执行成功,因此这段代码通过 TaskCompletionSource 模拟了类似 HTTP 的"同步等待"效果。

以下是详细的代码逻辑解析:

核心组件:_responseWaiters

csharp

编辑

复制代码
private readonly ConcurrentDictionary<string, TaskCompletionSource<MqttDeviceResponse>> _responseWaiters = new();
  • 作用:这是一个线程安全的字典,用来存放所有"正在等待回复"的请求。
  • Key (string) :消息的唯一 ID (msgId)。
  • Value (TaskCompletionSource) :这是一个 .NET 异步编程中的关键类。它相当于一个"占位符"或"承诺"。当你创建一个 Task 但还没结果时,可以用它来暂时挂着,等未来某个时刻(收到 MQTT 回复时)再手动把结果填进去,让等待的代码继续执行。
  • 为什么用 ConcurrentDictionary:因为 MQTT 的发送和接收通常在不同的线程或任务中运行,必须保证多线程安全。

接收端:OnInterceptingPublishAsync

复制代码
if (topic.Contains("respond", StringComparison.OrdinalIgnoreCase))
{
    var response = JsonSerializer.Deserialize<MqttDeviceResponse>(payloadStr);
    if (response != null && !string.IsNullOrWhiteSpace(response.msgId))
    {
        // 1. 拿着回复里的 msgId 去字典里找有没有人在等
        if (_responseWaiters.TryGetValue(response.msgId, out var tcs))
        {
            // 2. 找到了!把结果塞进去,唤醒等待的代码
            tcs.TrySetResult(response);
        }
    }
}
复制代码
public async Task<MqttDeviceResponse> SendCommandAndWaitAsync(...)
{
    var msgId = command.msgId;
    
    // 1. 创建一个"占位符",表示这个请求还没完成
    var tcs = new TaskCompletionSource<MqttDeviceResponse>(...);

    // 2. 把这个占位符存进字典,Key 是 msgId
    _responseWaiters[msgId] = tcs;

    try
    {
        // 3. 真正通过 MQTT 发送指令(发完立刻返回,不阻塞)
        await SendCommandAsync(deviceId, type, command);

        // 4. 【核心】开始死等!
        // 等待两个任务谁先完成:要么是收到回复(tcs.Task),要么是超时(Task.Delay)
        var waitTime = timeout ?? TimeSpan.FromSeconds(20);
        var completed = await Task.WhenAny(tcs.Task, Task.Delay(waitTime));

        // 5. 判断是谁赢了
        if (completed != tcs.Task)
        {
            throw new TimeoutException($"设备响应超时,msgId={msgId}");
        }

        // 6. 如果是 tcs.Task 赢了,说明收到了回复,返回结果
        return await tcs.Task;
    }
    finally
    {
        // 7. 无论成功还是超时,都要清理字典,防止内存泄漏
        _responseWaiters.TryRemove(msgId, out _);
    }
}
相关推荐
Seraphina3613 小时前
记一次实验:利用路径分隔符进行网页缓存欺骗
经验分享·笔记·安全·网络安全·缓存
InfinitePlus15 小时前
redis数据持久化
数据库·redis·缓存
weixin_4407305018 小时前
数据库缓存、索引、缓存命中率的理解
数据库·缓存
taocarts_bidfans1 天前
Taoify 站点缓存分层配置实操,解决海外访问卡顿与更新延迟
缓存·外贸独立站·taoify
Demons_kirit1 天前
PortSwigger网络缓存欺骗(3)
缓存
MetaLite1 天前
SpringBoot整合Caffeine-集群本地缓存如何保证分布式一致性
spring boot·分布式·缓存
01传说2 天前
redis开机自启脚本
数据库·redis·缓存
李可以量化2 天前
Redis Client 从了解到精通(二)上:redis-py 高级用法与核心命令实战
前端·数据库·redis·python·缓存·ptrade
仍然.2 天前
Redis---String
数据库·redis·缓存