Search code examples
c#taskblocked-threads

Cancel blocking AcceptTcpClient call


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)


Solution

  • The following code will close/abort AcceptTcpClient when isRunning variable becomes false

    public static bool isRunning;
    
    delegate void mThread(ref book isRunning);
    delegate void AccptTcpClnt(ref TcpClient client, TcpListener listener);
    
    public static main()
    {
       isRunning = true;
       mThread t = new mThread(StartListening);
       Thread masterThread = new Thread(() => t(this, ref isRunning));
       masterThread.IsBackground = true; //better to run it as a background thread
       masterThread.Start();
    }
    
    public static void AccptClnt(ref TcpClient client, TcpListener listener)
    {
      if(client == null)
        client = listener.AcceptTcpClient(); 
    }
    
    public static void StartListening(ref bool isRunning)
    {
      TcpListener listener = new TcpListener(new IPEndPoint(IPAddress.Any, portNum));
    
      try
      {
         listener.Start();
    
         TcpClient handler = null;
         while (isRunning)
         {
            AccptTcpClnt t = new AccptTcpClnt(AccptClnt);
    
            Thread tt = new Thread(() => t(ref handler, listener));
            tt.IsBackground = true;
            // the AcceptTcpClient() is a blocking method, so we are invoking it
            // in a separate dedicated thread 
            tt.Start(); 
            while (isRunning && tt.IsAlive && handler == null) 
            Thread.Sleep(500); //change the time as you prefer
    
    
            if (handler != null)
            {
               //handle the accepted connection here
            }        
            // as was suggested in comments, aborting the thread this way
            // is not a good practice. so we can omit the else if block
            // else if (!isRunning && tt.IsAlive)
            // {
            //   tt.Abort();
            //}                   
         }
         // when isRunning is set to false, the code exits the while(isRunning)
         // and listner.Stop() is called which throws SocketException 
         listener.Stop();           
      }
      // catching the SocketException as was suggested by the most
      // voted answer
      catch (SocketException e)
      {
    
      }
    
    }