【问题标题】:TcpClient.ConnectAsync or Socket.BeginConnect with non-blocking timeout setting具有非阻塞超时设置的 TcpClient.ConnectAsync 或 Socket.BeginConnect
【发布时间】:2015-04-21 03:31:22
【问题描述】:

目前我发现的所有解决方案都是基于 WaitOne: How to configure socket connect timeoutspawning a worker thread

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

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

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

【问题讨论】:

标签: c# .net sockets task-parallel-library async-await


【解决方案1】:

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;
}

【讨论】:

  • 谢谢,这有帮助。我不能做“使用”块,因为我的方法只连接(我没有控制连接的整个生命周期),但在这里并不那么重要。
  • ObjectDisposedException 应该是 NullReferenceException。见:connect.microsoft.com/VisualStudio/feedback/details/774269/…
  • 不要调用TcpClient.Dispose,而是考虑使用TcpClient.Client.Shutdown。这会关闭连接,导致原始操作完成而不会引发异常。然后可以安全地处置TcpClient
【解决方案2】:

如果您为超时创建第二个任务(Task.Delay 很好),那么您可以使用 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.
}

【讨论】:

  • 这不会中止任何事情。您可能想在某处输入CancellationToken
  • @JeroenMostert 没错,但这并不是真正有趣的地方。
  • 如果您将其放入生产代码中然后收到人们抱怨您的应用程序使机器耗尽端口的投诉很快就会变得非常有趣......该方法通常是可用的,但仅限于只要您记住,没有任何事情真正被中止,并且您不能不考虑这一点就开始新的操作。
  • @JeroenMostert 我并不是说端口没有关闭。
【解决方案3】:

我找到了一个使用

的解决方案

等待任务.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

【讨论】:

    猜你喜欢
    • 2015-01-15
    • 2015-06-18
    • 1970-01-01
    • 2012-11-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多