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);
            }
        }
    }