/// <summary> /// Send a message to the client. Cancelling <paramref name="ct"/> during WriteAsync /// will result in a lost connection. Thread-safe. /// </summary> /// <param name="msg"></param> /// <param name="ct"></param> /// <param name="log"></param> /// <returns>True if message sent. False if connection lost.</returns> public async Task <bool> WriteAsync(string msg, CancellationToken ct, bool log = false) { const string funcName = nameof(WriteAsync); if (ConnectionStatus == ConnectionStatusEnum.Disconnected) { return(false); } if (log) { Log.DebugEx(funcName, $"[{_logName}] Sending message {msg}", false); } // Add end chars here var msgBytes = Encoding.UTF8.GetBytes(msg + (_implicitEndOfMessageString ? _endOfMessageString : string.Empty)); await _sendMessageLock.WaitAsync(ct); // Lock try { using (ct.Register(() => _tcpStream.Dispose())) { // NetworkStream does not override WriteAsync. // This means that it will get the default behavior // of Stream.WriteAsync which just throws the token away after checking it initially. // Disposing _tcpStream cancels the write operation. // // Throw IOException when write fails. // Throw ObjectDisposedException when stream disposed. await _tcpStream.WriteAsync(msgBytes, 0, msgBytes.Length, ct); return(true); } } catch (Exception e) when(e is ObjectDisposedException || e is IOException) { _sendMessageFailedSignal.TryRelease(); ct.ThrowIfCancellationRequested(); return(false); } finally { _sendMessageLock.Release(); // Release } }