【问题标题】:Cancellation support for TcpClient.ConnectAsync methodTcpClient.ConnectAsync 方法的取消支持
【发布时间】:2021-09-01 01:32:03
【问题描述】:

我希望获得一些意见,以增加我正在处理的部分的弹性,该部分涉及异步创建多个 TcpClient 连接到各种服务器,一旦成功建立 TCP 连接,然后将 TcpClient 存储在排序中集合(只是要求的摘录)

这是我目前所拥有的:

internal class Program
{
    private static readonly TcpClient _client = new TcpClient(AddressFamily.InterNetwork);
    private static SslStream _stream;

    private static async Task Main(string[] args)
    {
        var timeoutTask = Task.Delay(5000);
        var connectTask = _client.ConnectAsync("10.14.16.24", 56564);

        var completedTask = await Task.WhenAny(timeoutTask, connectTask);
        if (completedTask == timeoutTask)
            _client.Dispose();

        try
        {
            await connectTask;
            _stream = new SslStream(_client.GetStream(), false);
        }
        catch (SocketException e)
        {
            Console.WriteLine(e.Message);
        }
    }
}

问题: 如果发生超时,connectTask 状态仍为“WaitingForActivation”,直到ConnectAsync 方法返回FaultedRanToCompletion。怎么取消这个任务,知道ConnectAsync不支持CancellationToken

欢迎提出任何建议或改进。 谢谢

【问题讨论】:

    标签: c# .net-core task tcpclient cancellation-token


    【解决方案1】:

    您的里程可能会根据您使用的.net 而有所不同

    using System;
    using System.Net.Sockets;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace TcpClientCancellation
    {
        public static class Extensions
        {
            public static async Task ConnectAsync(this TcpClient tcpClient, string host, int port, CancellationToken cancellationToken) {
                if (tcpClient == null) {
                    throw new ArgumentNullException(nameof(tcpClient));
                }
    
                cancellationToken.ThrowIfCancellationRequested();
    
                using (cancellationToken.Register(() => tcpClient.Close())) {
                    try {
                        cancellationToken.ThrowIfCancellationRequested();
    
                        await tcpClient.ConnectAsync(host, port).ConfigureAwait(false);
                    } catch (NullReferenceException) when (cancellationToken.IsCancellationRequested) {
                        cancellationToken.ThrowIfCancellationRequested();
                    }
                }
            }
        }
    }
    

    【讨论】:

    • NullReferenceExceptioncatch 怎么了?
    • 根据我的经验,当您调用 Close() 时,这是从 ConnectAsync 内部抛出的异常。
    • 在 .NET 5 中,您应该捕获 SocketException
    【解决方案2】:

    作为@Kelly Elton 答案的扩展,您似乎应该抓住

    • 在 .NET Framework 4.x 中NullReferenceException
    • 在 .NET Core 2.x 和 3.x 中ObjectDisposedException
    • 在.NET 5中SocketException

    所以也许正确的 catch 语句是

    #if NET5_0 //_OR_GREATER?
    catch (SocketException ex) when (ct.IsCancellationRequested)
    #elif NETCOREAPP2_0_OR_GREATER
    catch (ObjectDisposedException ex) when (ct.IsCancellationRequested)
    #elif NETFRAMEWORK && NET40_OR_GREATER
    catch (NullReferenceException ex) when (ct.IsCancellationRequested)
    #else
    #error Untested target framework, use with care!
    catch (???) when (ct.IsCancellationRequested)
    #endif
    

    【讨论】:

    • .NET 5.0 中现在有一个可取消的变体,万岁!
    【解决方案3】:

    我正在使用这些通用方法;他们可以为任何异步任务添加超时和取消令牌。您需要使用 using var tcpClient 来确保在取消异常引发后处理客户端或主动处理您的客户端

    如果您发现任何问题,请告诉我,以便我进行相应的修复。

    public static async Task<T> RunTask<T>(Task<T> task, int timeout = 0, CancellationToken cancellationToken = default)
    {
        await RunTask((Task)task, timeout, cancellationToken);
        return await task;
    }
    
    public static async Task RunTask(Task task, int timeout = 0, CancellationToken cancellationToken = default)
    {
        if (timeout == 0) timeout = -1;
    
        var timeoutTask = Task.Delay(timeout, cancellationToken);
        await Task.WhenAny(task, timeoutTask);
    
        cancellationToken.ThrowIfCancellationRequested();
        if (timeoutTask.IsCompleted)
            throw new TimeoutException();
    
        await task;
    }
    

    用法

    using var tcpClient = new TcpClient();
    await RunTask(tcpClient.ConnectAsync("yourhost.com", 443), cancellationToken: CancellationToken.None);
    

    【讨论】:

    • 只是一个小问题,但如果两个任务同时完成,你仍然将其视为超时。
    • @Ramon 好点,我们可以在超时前检查主任务的完成情况,但在大多数情况下,由于超时业务的性质,这并不重要,即使它真的发生了。跨度>
    猜你喜欢
    • 2015-03-20
    • 2021-06-17
    • 2021-12-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-18
    • 2020-01-13
    相关资源
    最近更新 更多