使用SocketAsyncEventArgs时TaskCompletionSource死锁问题排查
控制台异步TCP服务器死锁问题修复
你的代码核心问题是创建了两个不相关的TaskCompletionSource,导致等待的任务永远无法完成:
在AcceptClient方法中,你初始化了两个TaskCompletionSource<Socket>:
- 一个是
source,你最终await source.Task等待它完成 - 另一个是
new TaskCompletionSource<Socket>(),赋值给了args.UserToken
但在OnAcceptCompleted回调里,你完成的是args.UserToken对应的那个TaskCompletionSource,而不是你在等待的source。这就导致source.Task一直处于未完成状态,await永远卡住,代码无法执行到后续的打印逻辑。
同时你对线程的推测是错误的:SocketAsyncEventArgs.Completed事件默认就是在ThreadPool线程上触发的,和死锁无关。
修复步骤
- 把
args.UserToken直接赋值为你要等待的source,而非新建对象 - 在回调完成后,清理
SocketAsyncEventArgs的事件订阅和资源,避免内存泄漏 - 完善取消逻辑,确保CancellationToken能正确终止Socket操作
修正后的完整代码
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); Console.WriteLine("服务器已启动,等待连接..."); while (!_shutdownSource.IsCancellationRequested) { try { var socket = await AcceptClient(listener, _shutdownSource.Token).ConfigureAwait(false); Console.WriteLine($"Client accepted {socket.RemoteEndPoint}"); // 异步处理客户端连接,避免阻塞主线程 _ = HandleClientAsync(socket, _shutdownSource.Token); } catch (OperationCanceledException) { Console.WriteLine("服务器正在关闭..."); break; } catch (Exception ex) { Console.WriteLine($"接受客户端出错: {ex.Message}"); } } } private static async Task<Socket> AcceptClient(Socket listener, CancellationToken token) { var source = new TaskCompletionSource<Socket>(TaskCreationOptions.RunContinuationsAsynchronously); using var args = new SocketAsyncEventArgs(); // 将需要等待的TaskCompletionSource直接绑定到UserToken args.UserToken = source; args.Completed += OnAcceptCompleted; try { if (!listener.AcceptAsync(args)) { // 同步完成时直接触发回调逻辑 OnAcceptCompleted(listener, args); } using (token.Register(() => { source.TrySetCanceled(); // 主动取消Socket异步操作 listener.CancelConnectAsync(args); })) { return await source.Task.ConfigureAwait(false); } } finally { // 移除事件订阅,防止内存泄漏 args.Completed -= OnAcceptCompleted; } } private static void OnAcceptCompleted(object sender, SocketAsyncEventArgs args) { var source = (TaskCompletionSource<Socket>)args.UserToken; try { if (args.SocketError == SocketError.Success) { source.TrySetResult(args.AcceptSocket); } else if (args.SocketError == SocketError.OperationAborted) { source.TrySetCanceled(); } else { source.TrySetException(new InvalidOperationException($"Socket error = {args.SocketError}")); } } finally { // 清理Socket引用,避免资源占用 args.AcceptSocket = null; } } private static async Task HandleClientAsync(Socket socket, CancellationToken token) { using (socket) { try { var buffer = new byte[1024]; while (!token.IsCancellationRequested) { var received = await socket.ReceiveAsync(buffer, SocketFlags.None, token).ConfigureAwait(false); if (received == 0) { Console.WriteLine($"客户端 {socket.RemoteEndPoint} 断开连接"); break; } Console.WriteLine($"收到来自 {socket.RemoteEndPoint} 的数据: {System.Text.Encoding.UTF8.GetString(buffer, 0, received)}"); } } catch (OperationCanceledException) { Console.WriteLine($"客户端 {socket.RemoteEndPoint} 连接被取消"); } catch (Exception ex) { Console.WriteLine($"处理客户端 {socket.RemoteEndPoint} 出错: {ex.Message}"); } } } }
关键修改说明
TaskCompletionSource绑定修正:确保回调完成的是主线程等待的同一个任务,解决根本的等待阻塞问题- 资源泄漏防护:通过
using和finally块清理SocketAsyncEventArgs的事件订阅和资源 - 取消逻辑完善:在CancellationToken触发时,主动取消Socket操作,确保回调能正确响应取消状态
- 客户端处理优化:新增
HandleClientAsync方法,演示异步处理客户端读写的标准范式,避免阻塞主线程的连接接收逻辑
内容的提问来源于stack exchange,提问作者Zergatul
相关产品推荐
相关产品推荐

