Search code examples
c#tcplistenersocketexception

How do I resolve the unobserved task exception thrown by TcpListener


I wrote a sample unit test with TcpListener. It works fine in .NET Framework 4.8, but it fails in .Net8.0-windows.

TcpListener tcpListener;

TaskScheduler.UnobservedTaskException += HandleUnhandledException;

public void TestTcpListener()
{
    tcpListener = new TcpListener(new IPEndPoint(IPAddress.Any, 12345));
    tcpListener.Start();
    tcpListener.BeginAcceptTcpClient(null, null);
    tcpListener.Stop();
}

I subscribed to TaskScheduler's UnobservedTaskException event to catch the exception and then I run GC.Collect() And GC.WaitForPendingFinalizers() at the end of this UT(in TearDown method). I got this error message.

System.AggregateException: A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread. (The I/O operation has been aborted because of either a thread exit or an application request.)
 ---> System.Net.Sockets.SocketException (995): The I/O operation has been aborted because of either a thread exit or an application request.
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken)
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.System.Threading.Tasks.Sources.IValueTaskSource<System.Net.Sockets.Socket>.GetResult(Int16 token)
   at System.Net.Sockets.TcpListener.<AcceptTcpClientAsync>g__WaitAndWrap|32_0(ValueTask`1 task)
   --- End of inner exception stack trace ---


Solution

  • You need to observe the result of BeginAcceptTcpClient with a matching EndAcceptTcpClient

        public void TestTcpListener()
        {
            tcpListener = new TcpListener(new IPEndPoint(IPAddress.Any, 12345));
            tcpListener.Start();
            tcpListener.BeginAcceptTcpClient(MyCallback, null);
            tcpListener.Stop();
        }
    
        void MyCallback(IAsyncResult ar)
        {
            try
            {
                var client = tcpListener.EndAcceptTcpClient(ar);
            }
            catch(Exception ex)
            {
                // you should end up here!!!
                Console.WriteLine($"Callback handler: {ex.Message}");
            }
        }
    

    You can also do this via the return parameter from BeginAcceptTcpClient, but the callback approach is more common.

    You can also make use of the state parameter if you are concerned that the tcpListener field might have been wiped:

        public void TestTcpListener()
        {
            tcpListener = new TcpListener(new IPEndPoint(IPAddress.Any, 12345));
            tcpListener.Start();
            tcpListener.BeginAcceptTcpClient(MyCallback, tcpListener);
            tcpListener.Stop();
        }
    
        static void MyCallback(IAsyncResult ar)
        {
            try
            {
                var listener = (TcpListener)ar.AsyncState!;
                var client = listener.EndAcceptTcpClient(ar);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Callback handler: {ex.Message}");
            }
        }