基于 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 _);
    }
}
相关推荐
change_fate20 分钟前
升级依赖后,vite会自动重建缓存吗
缓存
饺子大魔王的男人33 分钟前
LitePan多网盘影音中枢:WebDAV挂载、STRM生成与缓存管理
缓存·dubbo
我不会起名字3224 小时前
Redis 缓存与数据库一致性:先删缓存还是先更新库的 4 种方案
数据库·redis·缓存·一致性·延迟双删
@#¥&~是乱码鱼啦5 小时前
ArkWeb开发手记02|权限、网络白名单与页面缓存控制
网络·缓存
拾贰_C1 天前
【English | conversation 】call | 抖音AI短句情景:打电话---come over
数据库·redis·缓存
ShineWinsu1 天前
对于Redis:缓存的解析
java·redis·缓存·缓存穿透·缓存击穿·缓存雪崩·缓存预热
害人终害己1 天前
redis修改密码的地方在哪里
数据库·redis·缓存
小静AI工程实验室1 天前
robots.txt 不是门禁:RFC 9309 规则、缓存与 Python 爬虫的 10 个边界
爬虫·python·缓存
for_ever_love__1 天前
缓存穿透、击穿、雪崩:三个经典问题与完整解决方案
java·数据库·redis·缓存·哈希算法·布隆过滤器·雪崩
hweiyu002 天前
Redis命令:HSTRLEN
redis·缓存