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

时间:2015-02-19 16:59:09

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

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

对我来说,用WaitOne阻塞线程会破坏异步方法的目的。产生另一个线程并不是更好(因为异步模型努力使用尽可能少的线程)。

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

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

3 个答案:

答案 0 :(得分:5)

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

但这并不取消原始操作,只允许您的代码表现得像一样。除非明确处理,否则被遗弃的行动将永远存在。

要实际取消基础操作,您应确保在Dispose上调用TcpClient(最好通过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;
}

答案 1 :(得分:1)

如果您为超时创建了第二个任务(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.
}

答案 2 :(得分:1)

我找到了一个使用

的解决方案
  

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