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