基于 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 _);
    }
}
相关推荐
John_Hui5 小时前
第 5 篇:Ubuntu 安装 Redis
数据库·redis·缓存
l1t6 小时前
DeepSeek总结的ALTER FUNCTION SET work_mem:无需全局更改即可修复 PostgreSQL 磁盘溢出
sql·缓存·postgresql
咖啡八杯6 小时前
Redis 工具封装:RedisCache 对 Operations 体系的二次封装
java·数据库·redis·缓存·框架·springboot·若依
l1t9 小时前
DeepSeek总结的PostgreSQL 中的 SQL 与 PL/pgSQL:计划缓存
sql·缓存·postgresql
hongyangcao11 小时前
Cloudflare GLM-5.3 串号事故复盘:KV 缓存页记账错了一个字段,155 条回复答进了别人的会话
人工智能·缓存·ai
MetaLite1 天前
Spring 同一个类里调用 @CacheEvict,为什么缓存没清掉?
java·spring·缓存
hweiyu001 天前
Redis命令:SMOVE
redis·缓存
可乐ea1 天前
Agent 隔离为什么失效:CPU 缓存成了跨机器信道
缓存·ai智能体·红队测试·沙箱安全·agent隔离·侧信道
谢白羽1 天前
从传统运维到 AI Infra: K8s 上调度 100 张 GPU踩坑笔记
运维·人工智能·缓存·kubernetes·vllm·sglang·vllm-omni