提问人:Zergatul 提问时间:8/23/2022 更新时间:8/23/2022 访问量:112
使用 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));
}
}
}
答:
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
正如您正确指出的,您正在使用两个不同的对象,而它们应该是相同的。所以这个对象从未完成。TaskCompletionSource
source
但是,使用原始套接字充满了困难,您应该改用 ,它可以为您处理所有这些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
}
}
评论