Asynchronous TCP server
This is an ugly old pattern. Why don't just try the newer async/await? You already use Task in your tests anyway.

In order to implement it the awaitable way you just need to use a different API, in this case AcceptTcpClientAsync and build everything on top of it. With the CancellationToken you can now better control the server.

public class TcpServer : IDisposable
{
    private readonly TcpListener _listener;
    private CancellationTokenSource _tokenSource;
    private bool _listening;    
    private CancellationToken _token;

    public event EventHandler&lt;DataReceivedEventArgs&gt; OnDataReceived;

    public TcpServer(IPAddress address, int port)
    {
        _listener = new TcpListener(address, port);
    }

    public bool Listening =&gt; _listening;

    public async Task StartAsync(CancellationToken? token = null)
    {
        _tokenSource = CancellationTokenSource.CreateLinkedTokenSource(token ?? new CancellationToken());
        _token = _tokenSource.Token;
        _listener.Start();
        _listening = true;

        try
        {
            while (!_token.IsCancellationRequested)
            {   
                await Task.Run(async () =&gt;
                {
                    var tcpClientTask = _listener.AcceptTcpClientAsync();
                    var result = await tcpClientTask;
                    OnDataReceived?.Invoke(this, new DataReceivedEventArgs(result.GetStream()));
                }, _token);
            }
        }
        finally
        {
            _listener.Stop();
            _listening = false;
        }
    }

    public void Stop()
    {
        _tokenSource?.Cancel();
    }

    public void Dispose()
    {
        Stop();
    }
}




EventArgs as a nested class, no no no ;-)



Test

The old test won't work anymore as now you need to make everything async/await.


the event handler now becomes async (sender, e)
Thread.Sleep is now await Task.Delay(3000);
the server needs to run async so you need a Task.Run(async () =&gt; {..}
at the end you wait for the server with await serverTask;


I used the console for output as I run this in LINQPad and added the thread-id so to see where it runs.

using (var server = new TcpServer(IPAddress.Any, 54001))
{
    server.OnDataReceived += async (sender, e) =&gt;
    {
        var bytesRead = 0;
        do
        {
            // Read buffer, discarding data
            bytesRead = e.Stream.Read(new byte[1024], 0, 1024);
        }
        while (bytesRead &gt; 0 &amp;&amp; e.Stream.DataAvailable);

        // Simulate long running task
        Console.WriteLine($"Doing some heavy response processing now. [{Thread.CurrentThread.ManagedThreadId}]");
        await Task.Delay(3000);
        Console.WriteLine($"Finished processing. [{Thread.CurrentThread.ManagedThreadId}]");

        var response = Encoding.ASCII.GetBytes("Who's there?");
        e.Stream.Write(response, 0, response.Length);
    };

    Task.Run(async () =&gt;
    {
        var serverTask = server.StartAsync();

        var tasks = new List&lt;Task&gt;();

        for (var i = 0; i &lt; 5; ++i)
        {
            tasks.Add(Task.Run(() =&gt;
            {
                var response = new byte[1024];

                using (var client = new TcpClient())
                {
                    client.Connect("127.0.0.1", 54001);

                    using (var stream = client.GetStream())
                    {
                        var request = Encoding.ASCII.GetBytes("Knock, knock...");
                        stream.Write(request, 0, request.Length);
                        stream.Read(response, 0, response.Length);

                        //Assert.AreEqual("Who's there?", Encoding.ASCII.GetString(response).TrimEnd('\0'));
                        Console.WriteLine($"Who's there? Echo: " + Encoding.ASCII.GetString(response).TrimEnd('\0') + $" [{Thread.CurrentThread.ManagedThreadId}]");
                    }
                }
            }));
        }

        //Assert.IsTrue(Task.WaitAll(tasks.ToArray(), 10000));
        Console.WriteLine($"IsTrue: " + Task.WaitAll(tasks.ToArray(), 10000));

        await serverTask;
    });

}