How to set a timeout for TcpClient.ConnectAsync()

TcpClient has no direct way to set the connection timeout. It doesn’t have any parameters that allow you to control it, and SendTimeout / ReceiveTimeout don’t apply to the initial connection.

The way I control the connection timeout is by awaiting a Task.WhenAny() with TcpClient.ConnectAsync() and Task.Delay(). Task.WhenAny() returns when any of the tasks complete.

There are 3 possible outcomes:

  • Task.ConnectAsync() completes and was successful.
  • Task.ConnectAsync() completes, but faulted. In this case, I want the exception to bubble up.
  • Task.Delay() completes, indicating the process has timed out.

See below for fully working code. This is a simple port tester (like doing “telnet IP PORT” just to see if a port is open).

TcpClientWrapper – specify the connection timeout

Here is the TcpClientWrapper class:

using System;
using System.Net.Sockets;
using System.Threading.Tasks;

namespace TcpClientTimeout
{
    public class TcpException : Exception
    {
        public TcpException(string msg) : base(msg) { }
    }
    public class TcpClientWrapper
    {
        public async Task ConnectAsync(string ip, int port, TimeSpan connectTimeout)
        {
            using (var tcpClient = new TcpClient())
            {
                var cancelTask = Task.Delay(connectTimeout);
                var connectTask = tcpClient.ConnectAsync(ip, port);

                //double await so if cancelTask throws exception, this throws it
                await await Task.WhenAny(connectTask, cancelTask);

                if (cancelTask.IsCompleted)
                {
                    //If cancelTask and connectTask both finish at the same time,
                    //we'll consider it to be a timeout. 
                    throw new TcpException("Timed out");
                }
            };
        }
    }
}
Code language: C# (cs)

If cancelTask completes first, it means the connection timed out, so it throws a custom TcpException.

Using the TcpClientWrapper

Here’s an example of how to use the TcpClientWrapper class:

using System;
using System.Threading.Tasks;

namespace TcpClientTimeout
{
    class Program
    {
        static void Main(string[] args)
        {
            Task.Run(TcpPortTest);
            Console.WriteLine("Please wait while the port is tested");
            Console.ReadKey();
        }
        static async Task TcpPortTest()
        {
            TcpClientWrapper tcpClientWrapper = new TcpClientWrapper();

            try
            {
                await tcpClientWrapper.ConnectAsync("127.0.0.1", 
                    12345, 
                    TimeSpan.FromSeconds(1));
                Console.WriteLine("Port tested - it's open");
            }
            catch(Exception ex)
            {
                Console.WriteLine($"Port tested - it's not open. Exception: {ex.Message}");
            }
        }
    }    
}

Code language: C# (cs)

Note: You can change this to either 1) wait for user input that specifies the connection info or 2) get the info as a command line argument.

Running the program

Here’s what this outputs when Task.Delay completes, and results in a timeout:

Please wait while the port is tested
Port tested - it's not open. Exception: Timed outCode language: plaintext (plaintext)

And here’s what it outputs when Task.ConnectAsync() fails to connect and throws an exception:

Please wait while the port is tested
Port tested - it's not open. Exception: No connection could be made because the target machine actively refused it 127.0.0.1:12345Code language: plaintext (plaintext)