使用 SocketAsyncEventArgs 时的 TaskCompletionSource 死锁

TaskCompletionSource deadlock while using SocketAsyncEventArgs

提问人:Zergatul 提问时间:8/23/2022 更新时间:8/23/2022 访问量:112

问:

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

我有一个假设:方法是由操作系统生成的内部调用的,它不是 的一部分。但我不知道如何解决它。OnAcceptCompletedThreadThreadPool

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));
        }
    }
}
C# 套接字异 async-await

评论


答:

0赞 Zergatul 8/23/2022 #1

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

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);
    }
}
2赞 Matt Bommicino 8/23/2022 #2

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

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


评论

0赞 Zergatul 8/23/2022
我正在为 .net standard 2.0 开发库,并且没有这样的重载。
0赞 Charlieface 8/23/2022
@Zergatul 那就用吧AcceptAsync(null, _shutdownSource.Token);
1赞 Charlieface 8/23/2022 #3

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

但是,使用原始套接字充满了困难,您应该改用 ,它可以为您处理所有这些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
    }
}