mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-19 11:03:07 +08:00
Refactor subscribe and unsubscribe to support MQTTv5.
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.PacketDispatcher
|
||||
{
|
||||
public interface IMqttPacketAwaiter : IDisposable
|
||||
{
|
||||
void Complete(MqttBasePacket packet);
|
||||
|
||||
void Fail(Exception exception);
|
||||
|
||||
void Cancel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.PacketDispatcher
|
||||
{
|
||||
public sealed class MqttPacketAwaiter<TPacket> : IMqttPacketAwaiter where TPacket : MqttBasePacket
|
||||
{
|
||||
private readonly TaskCompletionSource<MqttBasePacket> _taskCompletionSource = new TaskCompletionSource<MqttBasePacket>();
|
||||
private readonly ushort? _packetIdentifier;
|
||||
private readonly MqttPacketDispatcher _owningPacketDispatcher;
|
||||
|
||||
public MqttPacketAwaiter(ushort? packetIdentifier, MqttPacketDispatcher owningPacketDispatcher)
|
||||
{
|
||||
_packetIdentifier = packetIdentifier;
|
||||
_owningPacketDispatcher = owningPacketDispatcher ?? throw new ArgumentNullException(nameof(owningPacketDispatcher));
|
||||
}
|
||||
|
||||
public async Task<TPacket> WaitOneAsync(TimeSpan timeout)
|
||||
{
|
||||
using (var timeoutToken = new CancellationTokenSource(timeout))
|
||||
{
|
||||
timeoutToken.Token.Register(() => _taskCompletionSource.TrySetCanceled());
|
||||
|
||||
var packet = await _taskCompletionSource.Task.ConfigureAwait(false);
|
||||
return (TPacket)packet;
|
||||
}
|
||||
}
|
||||
|
||||
public void Complete(MqttBasePacket packet)
|
||||
{
|
||||
if (packet == null) throw new ArgumentNullException(nameof(packet));
|
||||
|
||||
// To prevent deadlocks it is required to call the _TrySetResult_ method
|
||||
// from a new thread because the awaiting code will not(!) be executed in
|
||||
// a new thread automatically (due to await). Furthermore _this_ thread will
|
||||
// do it. But _this_ thread is also reading incoming packets -> deadlock.
|
||||
Task.Run(() => _taskCompletionSource.TrySetResult(packet));
|
||||
}
|
||||
|
||||
public void Fail(Exception exception)
|
||||
{
|
||||
if (exception == null) throw new ArgumentNullException(nameof(exception));
|
||||
|
||||
Task.Run(() => _taskCompletionSource.TrySetException(exception));
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
Task.Run(() => _taskCompletionSource.TrySetCanceled());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_owningPacketDispatcher.RemovePacketAwaiter<TPacket>(_packetIdentifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.PacketDispatcher
|
||||
{
|
||||
public class MqttPacketDispatcher
|
||||
{
|
||||
private readonly ConcurrentDictionary<Tuple<ushort, Type>, IMqttPacketAwaiter> _packetAwaiters = new ConcurrentDictionary<Tuple<ushort, Type>, IMqttPacketAwaiter>();
|
||||
|
||||
private BlockingCollection<MqttBasePacket> _inboundPackagesQueue = new BlockingCollection<MqttBasePacket>();
|
||||
|
||||
public void Dispatch(Exception exception)
|
||||
{
|
||||
foreach (var awaiter in _packetAwaiters)
|
||||
{
|
||||
awaiter.Value.Fail(exception);
|
||||
}
|
||||
|
||||
_packetAwaiters.Clear();
|
||||
}
|
||||
|
||||
public void Dispatch(MqttBasePacket packet)
|
||||
{
|
||||
if (packet == null) throw new ArgumentNullException(nameof(packet));
|
||||
|
||||
ushort identifier = 0;
|
||||
if (packet is IMqttPacketWithIdentifier packetWithIdentifier && packetWithIdentifier.PacketIdentifier.HasValue)
|
||||
{
|
||||
identifier = packetWithIdentifier.PacketIdentifier.Value;
|
||||
}
|
||||
|
||||
var type = packet.GetType();
|
||||
var key = new Tuple<ushort, Type>(identifier, type);
|
||||
|
||||
if (_packetAwaiters.TryRemove(key, out var awaiter))
|
||||
{
|
||||
awaiter.Complete(packet);
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_inboundPackagesQueue)
|
||||
{
|
||||
_inboundPackagesQueue.Add(packet);
|
||||
}
|
||||
}
|
||||
|
||||
public MqttBasePacket Take(CancellationToken cancellationToken)
|
||||
{
|
||||
BlockingCollection<MqttBasePacket> inboundPackagesQueue;
|
||||
lock (_inboundPackagesQueue)
|
||||
{
|
||||
inboundPackagesQueue = _inboundPackagesQueue;
|
||||
}
|
||||
|
||||
return inboundPackagesQueue.Take(cancellationToken);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
foreach (var awaiter in _packetAwaiters)
|
||||
{
|
||||
awaiter.Value.Cancel();
|
||||
}
|
||||
|
||||
lock (_inboundPackagesQueue)
|
||||
{
|
||||
_inboundPackagesQueue?.Dispose();
|
||||
_inboundPackagesQueue = new BlockingCollection<MqttBasePacket>();
|
||||
}
|
||||
|
||||
_packetAwaiters.Clear();
|
||||
}
|
||||
|
||||
public MqttPacketAwaiter<TResponsePacket> AddPacketAwaiter<TResponsePacket>(ushort? identifier) where TResponsePacket : MqttBasePacket
|
||||
{
|
||||
if (!identifier.HasValue)
|
||||
{
|
||||
identifier = 0;
|
||||
}
|
||||
|
||||
var awaiter = new MqttPacketAwaiter<TResponsePacket>(identifier, this);
|
||||
|
||||
var key = new Tuple<ushort, Type>(identifier.Value, typeof(TResponsePacket));
|
||||
if (!_packetAwaiters.TryAdd(key, awaiter))
|
||||
{
|
||||
throw new InvalidOperationException($"The packet dispatcher already has an awaiter for packet of type '{key.Item2.Name}' with identifier {key.Item1}.");
|
||||
}
|
||||
|
||||
return awaiter;
|
||||
}
|
||||
|
||||
public void RemovePacketAwaiter<TResponsePacket>(ushort? identifier) where TResponsePacket : MqttBasePacket
|
||||
{
|
||||
if (!identifier.HasValue)
|
||||
{
|
||||
identifier = 0;
|
||||
}
|
||||
|
||||
var key = new Tuple<ushort, Type>(identifier.Value, typeof(TResponsePacket));
|
||||
_packetAwaiters.TryRemove(key, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user