private void TryPublishQueuedMessage(MqttManagedMessage message) { Exception transmitException = null; try { _mqttClient.PublishAsync(message.ApplicationMessage).GetAwaiter().GetResult(); lock (_messageQueue) //lock to avoid conflict with this.PublishAsync { //While publishing this message, this.PublishAsync could have booted this //message off the queue to make room for another (when using a cap //with the DropOldestQueuedMessage strategy). If the first item //in the queue is equal to this message, then it's safe to remove //it from the queue. If not, that means this.PublishAsync has already //removed it, in which case we don't want to do anything. _messageQueue.RemoveFirst(i => i.Id.Equals(message.Id)); } _storageManager?.RemoveAsync(message).GetAwaiter().GetResult(); } catch (MqttCommunicationException exception) { transmitException = exception; _logger.Warning(exception, $"Publishing application ({message.Id}) message failed."); if (message.ApplicationMessage.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce) { //If QoS 0, we don't want this message to stay on the queue. //If QoS 1 or 2, it's possible that, when using a cap, this message //has been booted off the queue by this.PublishAsync, in which case this //thread will not continue to try to publish it. While this does //contradict the expected behavior of QoS 1 and 2, that's also true //for the usage of a message queue cap, so it's still consistent //with prior behavior in that way. lock (_messageQueue) //lock to avoid conflict with this.PublishAsync { _messageQueue.RemoveFirst(i => i.Id.Equals(message.Id)); } } } catch (Exception exception) { transmitException = exception; _logger.Error(exception, $"Unhandled exception while publishing application message ({message.Id})."); } finally { ApplicationMessageProcessed?.Invoke(this, new MqttMessageProcessedEventArgs(message, transmitException)); } }