繁体   English   中英

使用 SocketAsyncEventArgs 时的 TaskCompletionSource 死锁

[英]TaskCompletionSource deadlock while using SocketAsyncEventArgs

我有简单的控制台 TCP 服务器应用程序,我正在尝试使其异步。 由于某种原因,我陷入僵局。 我从 PowerShell 运行Test-NetConnection localhost -Port 12345来触发传入连接。

我有一个假设: OnAcceptCompleted方法在操作系统产生的Thread内部被调用,它不是ThreadPool的一部分。 但我不知道如何解决它。

using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    private static CancellationTokenSource _shutdownSource = new CancellationTokenSource();

    static async Task Main()
    {
        using var listener = new Socket(SocketType.Stream, ProtocolType.Tcp);
        listener.Bind(new IPEndPoint(IPAddress.Any, 12345));
        listener.Listen(10);

        while (!_shutdownSource.IsCancellationRequested)
        {
            var socket = await AcceptClient(listener, _shutdownSource.Token).ConfigureAwait(false);
            // execution never go this line
            Console.WriteLine($"Client accepted {socket.RemoteEndPoint}");
        }
    }

    private static async Task<Socket> AcceptClient(Socket listener, CancellationToken token)
    {
        var source = new TaskCompletionSource<Socket>();
        var args = new SocketAsyncEventArgs();
        args.UserToken = new TaskCompletionSource<Socket>();
        args.Completed += OnAcceptCompleted;
        if (!listener.AcceptAsync(args))
        {
            OnAcceptCompleted(listener, args);
        }

        using (token.Register(() => source.TrySetCanceled()))
        {
            // we don't need ConfigureAwait(false) here, but just to be sure
            return await source.Task.ConfigureAwait(false);
        }
    }

    private static void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
    {
        var source = (TaskCompletionSource<Socket>)args.UserToken;
        if (args.SocketError == SocketError.Success)
        {
            // checked in debugger, TaskCompletionSource goes to RanToCompletion status here
            source.TrySetResult(args.AcceptSocket);
        }
        else if (args.SocketError == SocketError.OperationAborted)
        {
            source.TrySetCanceled();
        }
        else
        {
            source.TrySetException(new InvalidOperationException("Socket error = " + args.SocketError));
        }
    }
}

我不明白为什么你需要底部的两种方法。 为什么这是不可接受的?

static async Task Main()
    {
        using var listener = new Socket(SocketType.Stream, ProtocolType.Tcp);
        listener.Bind(new IPEndPoint(IPAddress.Any, 12345));
        listener.Listen(10);

        while (!_shutdownSource.IsCancellationRequested)
        {
            var socket = await listener.AcceptAsync(_shutdownSource.Token);
            Console.WriteLine($"Client accepted {socket.RemoteEndPoint}");
        }
    }


正如您正确指出的那样,您正在使用两个不同TaskCompletionSource对象,而它们应该是相同的。 所以source object 从未完成。

但是,使用原始 sockets 充满了困难,您应该改用TcpListener ,它会为您处理所有这些

class Program
{
    private static CancellationTokenSource _shutdownSource = new CancellationTokenSource();

    static async Task Main()
    {
        var listener = new TcpListener(IPAddress.Any, 12345);
        try
        {
            listener.Start(10);

            while (!_shutdownSource.IsCancellationRequested)
            {
                var client = await listener.AcceptTcpClientAsync(_shutdownSource.Token).ConfigureAwait(false);
                Console.WriteLine($"Client accepted {client.RemoteEndPoint}");
                // hand off client to another function
                // Task.Run(() => HandleYourClient(client, _shutdownSource.Token));
                // otherwise `client` needs `using` above to dispose
            }
        }
        catch (OperationCanceledException)
        { //
        }
        finally
        {
            if (listener.Active)
                listener.Stop()
        }
    }

    private async Task HandleClient(TcpClient client, CancellationToken token)
    {
        using client;
        using var stream = client.GetStream();
        // do stuff
    }
}

对不起这是我的错。 我在AcceptClient方法中创建 2 TaskCompletionSource<Socket> 固定代码:

private static async Task<Socket> AcceptClient(Socket listener, CancellationToken token)
{
    var source = new TaskCompletionSource<Socket>();
    var args = new SocketAsyncEventArgs();
    args.UserToken = source;
    args.Completed += OnAcceptCompleted;
    if (!listener.AcceptAsync(args))
    {
        OnAcceptCompleted(listener, args);
    }

    using (token.Register(() => source.TrySetCanceled()))
    {
        return await source.Task.ConfigureAwait(false);
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM