由于 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 _);
}
}


