TcpClient.ConnectAsync 或 Socket.BeginConnect 具有非阻塞超时设置

TcpClient.ConnectAsync or Socket.BeginConnect with non-blocking timeout setting

到目前为止我找到的所有解决方案都基于 WaitOne: How to configure socket connect timeoutspawning a worker thread

对我来说,用 WaitOne 阻塞线程违背了异步方法的目的。产生另一个线程也好不了多少(因为异步模型力求使用尽可能少的线程)。

是否有任何其他解决方案仍然可以让我在不阻塞当前线程或产生另一个线程的情况下中止连接尝试?

我正在开发一个由外部代码使用的库,它不知道我的库内部发生了什么(代码只调用我的 ConnectAsync 方法,其余的由我完成,包括 TcpClient.ConnectAsync 等等)。外部代码可以是任何东西(网络应用程序、桌面应用程序、Windows 服务等等)。理想情况下,解决方案不应要求外部代码执行任何操作来中止操作,而不仅仅是设置我的 class 的 .Timeout 属性。但是,如果这是在实现自定义连接超时时避免阻塞或工作线程的唯一方法,我将很高兴看到我有哪些选项(根据 async/await 模型)。

如果您为超时创建第二个任务(Task.Delay does nicely), then you can use Task.WhenAny 在您的任务或超时完成后立即完成。

var timeout = Task.Delay(whatever);
var mightTimeout = Task.WhenAny(new {} { timeout, originalTask });

// let mightTimeout complete by whatever method (eg. async)

if (mightTimeout == timeout) {
  // Timeout!!
  // Put abort code in here.
}

TcpClient.SendAsync 没有收到 CancellationToken 所以它不能 真的 被取消 (How do I cancel non-cancelable async operations?)。您可以使用 WithTimeout 扩展方法:

public static Task<TResult> WithTimeout<TResult>(this Task<TResult> task, TimeSpan timeout)
{
    var timeoutTask = Task.Delay(timeout).ContinueWith(_ => default(TResult), TaskContinuationOptions.ExecuteSynchronously);
    return Task.WhenAny(task, timeoutTask).Unwrap();
}

但这并没有取消原来的操作,只是让你的代码表现得像它那样。除非明确处理,否则放弃的操作将永远存在。

要真正取消基础操作,您应该确保在 TcpClient 上调用 Dispose(最好通过 using 作用域)。这将使被放弃的任务抛出 ObjectDisposedException(或其他),因此请注意这一点。

你可以看看我的 answer here 关于使用 TimeoutScope:

try
{
    var client = new TcpClient();
    using (client.CreateTimeoutScope(TimeSpan.FromSeconds(2)))
    {
        var result = await client.ConnectAsync();
        // Handle result
    }
}
catch (ObjectDisposedException)
{
    return null;
}

我找到了使用

的解决方案

Await Task.WhenAny

只要包含的任何任务先完成,

Task.WhenAny 就会完成。 把它放在一个异步函数中

这是一个适合我的例子:

Public Async Function TCPConnectionAsync(HostIpAddress, Port) As Task(Of String)
 Dim client As New TcpClient     
  Await Task.WhenAny(client.ConnectAsync(HostIpAddress, Porta), Task.Delay(3000)) 
 'this above will not block because function is async,
 'whenever the connection is successful or Task.Delay expires the task will end
 ' proceeding next, where you can check the connection 


 If client.Connected = False Then 'this will be evaluated after 3000ms
      client.Close()
      return "not connected"
 else
      'do here whatever you need with the client connection


       client.Close()
       return "all done"
 End If
End Sub