Cancel blocking AcceptTcpClient call

user1641096 picture user1641096 · Sep 2, 2012 · Viewed 28.8k times · Source

As everyone may already know, the simplest way to accept incoming TCP connections in C# is by looping over TcpListener.AcceptTcpClient(). Additionally this way will block code execution until a connection is obtained. This is extremely limiting to a GUI, so I want to listen for connections in either a seperate thread or task.

I have been told, that threads have several disadvantages, however nobody explained me what these are. So instead of using threads, I used tasks. This works great, however since the AcceptTcpClient method is blocking execution, I can't find any way of handling a task cancellation.

Currently the code looks like this, but I have no idea how I would want to cancel the task when I want the program to stop listening for connections.

First off the function executed in the task:

static void Listen () {
// Create listener object
TcpListener serverSocket = new TcpListener ( serverAddr, serverPort );

// Begin listening for connections
while ( true ) {
    try {
        serverSocket.Start ();
    } catch ( SocketException ) {
        MessageBox.Show ( "Another server is currently listening at port " + serverPort );
    }

    // Block and wait for incoming connection
    if ( serverSocket.Pending() ) {
        TcpClient serverClient = serverSocket.AcceptTcpClient ();
        // Retrieve data from network stream
        NetworkStream serverStream = serverClient.GetStream ();
        serverStream.Read ( data, 0, data.Length );
        string serverMsg = ascii.GetString ( data );
        MessageBox.Show ( "Message recieved: " + serverMsg );

        // Close stream and TcpClient connection
        serverClient.Close ();
        serverStream.Close ();

        // Empty buffer
        data = new Byte[256];
        serverMsg = null;
    }
}

Second, the functions starting and stopping the listening service:

private void btnListen_Click (object sender, EventArgs e) {
    btnListen.Enabled = false;
    btnStop.Enabled = true;
    Task listenTask = new Task ( Listen );
    listenTask.Start();
}

private void btnStop_Click ( object sender, EventArgs e ) {
    btnListen.Enabled = true;
    btnStop.Enabled = false;
    //listenTask.Abort();
}

I just need something to replace the listenTask.Abort() call (Which I commented out because the method doesn't exist)

Answer

BitMask777 picture BitMask777 · May 30, 2013

Cancelling AcceptTcpClient

Your best bet for cancelling the blocking AcceptTcpClient operation is to call TcpListener.Stop which will throw a SocketException that you can catch if you want to explicitly check that the operation was cancelled.

       TcpListener serverSocket = new TcpListener ( serverAddr, serverPort );

       ...

       try
       {
           TcpClient serverClient = serverSocket.AcceptTcpClient ();
           // do something
       }
       catch (SocketException e)
       {
           if ((e.SocketErrorCode == SocketError.Interrupted))
           // a blocking listen has been cancelled
       }

       ...

       // somewhere else your code will stop the blocking listen:
       serverSocket.Stop();

Whatever wants to call Stop on your TcpListener will need some level of access to it, so you would either scope it outside of your Listen method or wrap your listener logic inside of an object that manages the TcpListener and exposes Start and Stop methods (with Stop calling TcpListener.Stop()).

Async Termination

Because the accepted answer uses Thread.Abort() to terminate the thread it might be helpful to note here that the best way to terminate an asynchronous operation is by cooperative cancellation rather than a hard abort.

In a cooperative model, the target operation can monitor a cancellation indicator which is signaled by the terminator. This allows the target to detect a cancellation request, clean up as needed, and then at an appropriate time communicate status of the termination back to the terminator. Without an approach like this, abrupt termination of the operation can leave the thread's resources and possibly even the hosting process or app domain in a corrupt state.

From .NET 4.0 onward, the best way to implement this pattern is with a CancellationToken. When working with threads the token can be passed in as a parameter to the method executing on the thread. With Tasks, support for CancellationTokens is built into several of the Task constructors. Cancellation tokes are discussed in more detail in this MSDN article.