private async void WriterLoop(TcpClient client)
        {
            try
            {
                while (true)
                {
                    byte[] msg;
                    lock (_lock)
                    {
                        if (_client != client)
                        {
                            return;
                        }
                        msg = _messageQueue.Peek();
                    }
                    await TcpTransport.SendFramed(client.GetStream(), msg).ConfigureAwait(false);

                    lock (_lock)
                    {
                        if (_messageQueue.Peek() == msg)
                        {
                            _messageQueue.Dequeue();
                        }
                        if (_client != client)
                        {
                            return;
                        }
                        if (_messageQueue.Count == 0)
                        {
                            _logger.LogDebug("Message queue for {TargetEndPoint} empty, switching to state {ClientState}.", TargetEndPoint, State.Connected);
                            _state = State.Connected;
                            // a bit of paranoia:
                            if (_keepAliveTimer != null)
                            {
                                _keepAliveTimer.Dispose();
                            }
                            _keepAliveTimer = new Timer(_ => SendKeepAlive(client), null, Transport.KeepAliveInterval, Transport.KeepAliveInterval);
                            return;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                OnCloseOrError(client, ex);
            }
        }
        private async void SendKeepAlive(TcpClient client)
        {
            try
            {
                lock (_lock)
                {
                    if (_client != client || _state != State.Connected)
                    {
                        return;
                    }
                    // to block any concurrent attempts to send, since only one thread may send over a TcpClient at one time.
                    _state = State.Sending;
                }
                _logger.LogDebug("Sending keep-alive to {TargetEndPoint}", TargetEndPoint);
                await TcpTransport.SendFramed(client.GetStream(), new byte[0]).ConfigureAwait(false);

                lock (_lock)
                {
                    if (_client != client || _state != State.Sending)
                    {
                        return;
                    }
                    if (_messageQueue.Count > 0)
                    {
                        StartSending();
                    }
                    else
                    {
                        _state = State.Connected;
                    }
                }
            }
            catch (Exception ex)
            {
                OnCloseOrError(client, ex);
            }
        }