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

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)

Using the TcpClientWrapper

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)

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 out
Code 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:12345
Code language: plaintext (plaintext)

Leave a Comment