Just looking for feedback on correctness of my understanding of async/await. Curious about the Task.Run inside of the StartReceive() method. Resharper (or maybe just the compiler) warns against doing this without an await, but I think in this case, its ok. After running, one can connect by telnetting to localhost 7776. Thanks for your reply Svick. I've added a second version with suggestions you provided. A couple of things: - This is just an experiment, not production. That said, I would like to make it quality. - I understand the need to avoid void async methods (I saw the talk on channel 9). But at some point up the chain of method calls, we reach a point where async is no possible. In this case, Main() cannot be async. Any ideas for handling this? - Also, when cleaning up, some of the async methods throw exceptions with disposing/closing the object they belong to. Is catching and maybe logging or even swallowing these exceptions the best way to handle that clean up? I cannot figure out a better way at the moment. **V3** (Better handling of clean up on server stop): using System; using System.Collections.Concurrent; using System.IO; using System.Net; using System.Net.Sockets; using System.Runtime.CompilerServices; using System.Threading.Tasks; namespace ChatTutorial.ConsoleServer { class Program { private static void Main() { var server = new ChatServer(); server.Start(); Console.WriteLine("Listening....Press Enter to Stop."); Console.ReadLine(); server.Stop(); Console.WriteLine("Server Stopped. Press Enter to exit."); Console.ReadLine(); } } internal class ChatServer { private readonly TcpListener _listener = new TcpListener(IPAddress.Any, 6667); private readonly ConcurrentDictionary<string, LocalClient> _clients = new ConcurrentDictionary<string, LocalClient>(); private bool _stopping; public async void Start() { try { _listener.Start(); while (!_stopping) { try { Console.WriteLine("Listening..."); var client = await _listener.AcceptTcpClientAsync(); if (client == null) break; Console.WriteLine("Client connected"); var id = Guid.NewGuid().ToString(); if (_clients.TryAdd(id, new LocalClient(client, ReceivedMessage, ClientClosed, id))) continue; Console.WriteLine("Client {0} already exists in dictionary", id); client.Close(); } catch (InvalidOperationException) { if (!_stopping) { Console.WriteLine("InvalidOperationException"); } break; } catch (SocketException ex) { if (!_stopping) { Console.WriteLine("SocketException"); Logger.PrintException(ex); } break; } catch (Exception ex) { Logger.PrintException(ex); break; } } } catch (Exception ex) { Logger.PrintException(ex); } } public void Stop() { Console.WriteLine("Stopping..."); _stopping = true; _listener.Stop(); if (_clients.Count <= 0) return; foreach (var entry in _clients) entry.Value.Dispose(); } public void ClientClosed(LocalClient client) { Console.WriteLine("Client {0} disconnected", client.Id); LocalClient tempLocalClient; _clients.TryRemove(client.Id, out tempLocalClient); client.Dispose(); Console.WriteLine("Client {0} Closed", client.Id); ReceivedMessage("Client disconnected", client.Id); } public void ReceivedMessage(string message, string id) { try { foreach (var client in _clients) { if (client.Key != id) client.Value.Send(id + "> " + message); } } catch (Exception ex) { Logger.PrintException(ex); } } internal class LocalClient : IDisposable { private readonly TcpClient _client; public TcpClient Client { get { return _client; } } private readonly Action<LocalClient> _closedCallback; private readonly Action<string,string> _recvCallback; private readonly StreamReader _reader; private readonly StreamWriter _writer; private readonly string _id; public string Id { get { return _id; } } private bool _disposing; public LocalClient(TcpClient client, Action<string,string> recvCallback, Action<LocalClient> closedCallback, string id) { _closedCallback = closedCallback; try { _client = client; _recvCallback = recvCallback; _id = id; _reader = new StreamReader(_client.GetStream()); _writer = new StreamWriter(_client.GetStream()) {AutoFlush = true}; StartReceive(); Console.WriteLine("Local client {0} receiving...", id); } catch (Exception ex) { Logger.PrintException(ex); } } public async void StartReceive() { try { while (true) { if (_reader == null) return; var message = await _reader.ReadLineAsync(); if (String.IsNullOrEmpty(message)) { Task.Run(() => _closedCallback(this)); return; } _recvCallback(message, _id); Console.WriteLine("{0} > {1}", _id, message); } } catch (ArgumentOutOfRangeException) { if(!_disposing) Console.WriteLine("ArgumentOutOfRangeException"); } catch (ObjectDisposedException) { if (!_disposing) Console.WriteLine("ObjectDisposedException"); } catch (InvalidOperationException) { if (!_disposing) Console.WriteLine("InvalidOperationException"); } catch (Exception ex) { Logger.PrintException(ex); } } public async void Send(string message) { try { if (String.IsNullOrEmpty(message)) return; if (_writer == null) return; await _writer.WriteLineAsync(message); } catch (Exception ex) { Logger.PrintException(ex); } } public void Dispose() { _disposing = true; if (_reader != null) _reader.Dispose(); if (_writer != null) _writer.Dispose(); } } } public static class Logger { public static void PrintException(Exception ex, [CallerMemberName]string memberName = "") { Console.WriteLine("{0}: \n '{1}' \n '{2}'", memberName, ex.Message, ex.StackTrace); } } } **V2** (with suggestions from svick): using System; using System.Collections.Concurrent; using System.IO; using System.Net; using System.Net.Sockets; using System.Runtime.CompilerServices; using System.Threading.Tasks; namespace ChatTutorial.ConsoleServer { class Program { private static void Main() { var server = new ChatServer(); server.Start(); Console.WriteLine("Listening....Press Enter to Stop."); Console.ReadLine(); server.Stop(); Console.WriteLine("Server Stopped. Press Enter to exit."); Console.ReadLine(); } } internal class ChatServer { private readonly TcpListener _listener = new TcpListener(IPAddress.Any, 6667); private readonly ConcurrentDictionary<string, LocalClient> _clients = new ConcurrentDictionary<string, LocalClient>(); private bool _stopping; public async void Start() { try { _listener.Start(); while (!_stopping) { try { Console.WriteLine("Listening..."); var client = await _listener.AcceptTcpClientAsync(); if (client == null) break; Console.WriteLine("Client connected"); var id = Guid.NewGuid().ToString(); if (_clients.TryAdd(id, new LocalClient(client, ReceivedMessage, ClientClosed, id))) continue; Console.WriteLine("Client {0} already exists in dictionary", id); client.Close(); } catch (InvalidOperationException) { Console.WriteLine("InvalidOperationException"); break; } catch (SocketException ex) { Console.WriteLine("SocketException"); Logger.PrintException(ex); break; } catch (Exception ex) { Logger.PrintException(ex); break; } } } catch (Exception ex) { Logger.PrintException(ex); } } public void Stop() { Console.WriteLine("Stopping..."); _stopping = true; _listener.Stop(); if (_clients.Count <= 0) return; foreach (var entry in _clients) entry.Value.Dispose(); } public void ClientClosed(LocalClient client) { Console.WriteLine("Client {0} disconnected", client.Id); LocalClient tempLocalClient; _clients.TryRemove(client.Id, out tempLocalClient); client.Dispose(); Console.WriteLine("Client {0} Closed", client.Id); ReceivedMessage("Client disconnected", client.Id); } public void ReceivedMessage(string message, string id) { try { foreach (var client in _clients) { if (client.Key != id) client.Value.Send(id + "> " + message); } } catch (Exception ex) { Logger.PrintException(ex); } } internal class LocalClient : IDisposable { private readonly TcpClient _client; public TcpClient Client { get { return _client; } } private readonly Action<LocalClient> _closedCallback; private readonly Action<string,string> _recvCallback; private readonly StreamReader _reader; private readonly StreamWriter _writer; private readonly string _id; public string Id { get { return _id; } } public LocalClient(TcpClient client, Action<string,string> recvCallback, Action<LocalClient> closedCallback, string id) { _closedCallback = closedCallback; try { _client = client; _recvCallback = recvCallback; _id = id; _reader = new StreamReader(_client.GetStream()); _writer = new StreamWriter(_client.GetStream()) {AutoFlush = true}; StartReceive(); Console.WriteLine("Local client {0} receiving...", id); } catch (Exception ex) { Logger.PrintException(ex); } } public async void StartReceive() { try { while (true) { if (_reader == null) return; var message = await _reader.ReadLineAsync(); if (String.IsNullOrEmpty(message)) { Task.Run(() => _closedCallback(this)); return; } _recvCallback(message, _id); Console.WriteLine("{0} > {1}", _id, message); } } catch (ArgumentOutOfRangeException) { Console.WriteLine("ArgumentOutOfRangeException"); } catch (ObjectDisposedException) { Console.WriteLine("ObjectDisposedException"); } catch (InvalidOperationException) { Console.WriteLine("InvalidOperationException"); } catch (Exception ex) { Logger.PrintException(ex); } } public async void Send(string message) { try { if (String.IsNullOrEmpty(message)) return; if (_writer == null) return; await _writer.WriteLineAsync(message); } catch (Exception ex) { Logger.PrintException(ex); } } public void Dispose() { if (_reader != null) _reader.Dispose(); if (_writer != null) _writer.Dispose(); } } } public static class Logger { public static void PrintException(Exception ex, [CallerMemberName]string memberName = "") { Console.WriteLine("{0}: \n '{1}' \n '{2}'", memberName, ex.Message, ex.StackTrace); } } } **V1:** using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Sockets; using System.Threading.Tasks; namespace ChatTutorial.ConsoleServer { class Program { static void Main(string[] args) { try { var server = new ChatServer(); server.Start(); Console.WriteLine("Listening...."); Console.ReadLine(); } catch (Exception ex) { Logger.PrintException(ex, "Main"); } } } internal class ChatServer { private readonly TcpListener _listener = new TcpListener(IPAddress.Parse("0.0.0.0"), 7776); private readonly Dictionary<string, LocalClient> _clients = new Dictionary<string, LocalClient>(); public async void Start() { try { _listener.Start(); while (true) { try { Console.WriteLine("Listening..."); var client = await _listener.AcceptTcpClientAsync(); Console.WriteLine("Client connected"); var id = Guid.NewGuid().ToString(); _clients.Add(id, new LocalClient(client, ReceivedMessage, ClientClosed, id)); } catch (Exception ex) { Console.WriteLine("Start() While() '{0}', '{1}'", ex.Message, ex.StackTrace); } } } catch (Exception ex) { Logger.PrintException(ex, "Start"); } } public void ClientClosed(string id) { Console.WriteLine("Client {0} disconnected", id); var client = _clients[id]; _clients.Remove(id); client.Close(); Console.WriteLine("Client {0} Closed", id); ReceivedMessage("Client disconnected", id); } public void ReceivedMessage(string message, string id) { try { var clientsToRemove = new List<string>(); foreach (var client in _clients) { if (client.Value != null && client.Value.Client.Connected) { if (client.Key != id) client.Value.Send(id + "> " + message); } else clientsToRemove.Add(client.Key); } foreach (var client in clientsToRemove) _clients.Remove(client); } catch (Exception ex) { Logger.PrintException(ex, "ReceivedMessage"); } } internal class LocalClient { private readonly TcpClient _client; public TcpClient Client { get { return _client; } } private readonly Action<string> _closedCallback; private readonly Action<string,string> _recvCallback; private readonly StreamReader _reader; private readonly StreamWriter _writer; private readonly string _id; public string Id { get { return _id; } } public LocalClient(TcpClient client, Action<string,string> recvCallback, Action<string> closedCallback, string id) { _closedCallback = closedCallback; try { _client = client; _recvCallback = recvCallback; _id = id; _reader = new StreamReader(_client.GetStream()); _writer = new StreamWriter(_client.GetStream()) {AutoFlush = true}; StartReceive(); Console.WriteLine("Local client {0} receiving...", id); } catch (Exception ex) { Logger.PrintException(ex, "LocalClient"); } } public async void StartReceive() { try { while (true) { var message = await _reader.ReadLineAsync(); if (String.IsNullOrEmpty(message)) { Task.Run(() => _closedCallback(_id)); return; } _recvCallback(message, _id); Console.WriteLine("{0} > {1}", _id, message); } } catch (Exception ex) { Logger.PrintException(ex, "StartReceive"); } } public void Close() { if (_reader != null) _reader.Dispose(); } public async void Send(string message) { try { if (String.IsNullOrEmpty(message)) return; await _writer.WriteLineAsync(message); } catch (Exception ex) { Logger.PrintException(ex, "Send"); } } } } public static class Logger { public static void PrintException(Exception ex, string methodName) { Console.WriteLine("{0}: \n '{1}' \n '{2}'", methodName, ex.Message, ex.StackTrace); } } }