基于 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 _);
    }
}
相关推荐
HanhahnaH3 小时前
Redis单线程和Tair多线程架构设计对比
分布式·缓存
wdfk_prog8 小时前
ROS教程07:从 ros::start() 顺着源码读懂 Master、XML-RPC 与 Topic 注册发现
运维·缓存·docker·容器·ros
Escalating_xu9 小时前
【内存管理发展史】从 8086 实模式到分页:CPU 如何把虚拟地址变成物理地址
linux·缓存
wdfk_prog9 小时前
用 Git Submodule + Sparse Checkout 管理 RT-Thread:内核、BSP、第三方库与业务代码分层实践
运维·缓存·docker·容器·ros
努力努力再努力wz9 小时前
【Docker入门系列】镜像为什么能复用?一文吃透 Docker Image、Registry、运行时架构与常用命令
缓存·docker·容器
shark-chili12 小时前
关于AI辅助编程的认知
数据库·人工智能·redis·macos·缓存
oioihoii13 小时前
在 Kubernetes 上管好数据库:金仓 KES-Operator 正式落地
缓存
wdfk_prog14 小时前
ROS教程06:ROS1 Node 启动与停止流程
运维·缓存·docker·容器·ros
Doris__HE16 小时前
【元脑服务器NF8480G7-NF8480M7技术规格分享】
运维·服务器·数据库·缓存·性能优化
吉甫作诵1 天前
Redis 常用命令大全:11 大类命令速查手册
运维·数据库·redis·缓存·nosql