C#异步客户端Socket示例原理、Task改造及ReceiveAsync await问题
问题1:APM模式Socket示例的异步判定与改写
首先明确:异步的核心是IO操作期间不占用线程池线程等待,而非调用端完全不阻塞。你看到的WaitOne是示例为了同步执行流程(保证连接完成后再发送数据)做的调用端阻塞,底层IO逻辑仍然是异步实现:
- 调用
BeginConnect之后,操作系统会直接接管TCP连接请求,.NET不会占用任何线程等待连接结果,当前线程只是在WaitOne处进入休眠挂起状态,不会消耗CPU资源 - 连接完成后操作系统会推送完成信号,触发
ConnectCallback回调,回调内部调用connectDone.Set()发送信号,才会唤醒WaitOne处的线程继续执行后续逻辑
和同步Connect的核心区别是:同步Connect会让当前线程一直处于内核态轮询等待IO结果,CPU占用率高;而异步模式下WaitOne只是挂起线程,不消耗CPU,且如果不需要同步执行流程,可以直接去掉WaitOne,后续逻辑全部写在回调中,调用线程可以直接执行其他任务,这就是异步的核心价值。官方示例里加WaitOne只是为了让示例代码的执行顺序和同步代码一致,方便开发者理解流程,实际异步开发场景中很少主动阻塞等待。
可以用Task+Wait的方式改写这段代码,.NET已经为APM(异步编程模型,即Begin/End配对的模式)提供了官方的Task封装方法TaskFactory.FromAsync,不需要自己维护ManualResetEvent对象,改写示例如下:
// 连接逻辑转Task,需要阻塞的话把await换成.Wait()即可 await Task.Factory.FromAsync(client.BeginConnect, client.EndConnect, remoteEP, null); // 发送逻辑如果是APM实现也可以用同样方式转换 byte[] sendBuffer = Encoding.UTF8.GetBytes("This is a test<EOF>"); await Task.Factory.FromAsync(client.BeginSend, client.EndSend, sendBuffer, 0, sendBuffer.Length, SocketFlags.None, null);
问题2:SocketAsyncEventArgs模式ReceiveAsync无法await的问题
你调用的ReceiveAsync(SocketAsyncEventArgs)是基于事件的异步模式(EAP),本身返回值是bool类型,不是可等待的Task类型,自然不能直接用await。这个返回值的含义是:返回true代表IO操作正在挂起执行,完成后会触发你注册的Completed事件;返回false代表IO操作已经同步完成,不会触发Completed事件,可以直接读取结果。
要让它支持await,可以通过TaskCompletionSource封装成可等待的Task,示例代码如下:
private async Task<bool> handleReceiveAsync() { byte[] frameLenData = new byte[4]; SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs(); socketEventArg.SetBuffer(frameLenData, 0, 4); var tcs = new TaskCompletionSource<SocketError>(); socketEventArg.Completed += (s, e) => tcs.TrySetResult(e.SocketError); bool pending = _socket.ReceiveAsync(socketEventArg); // 处理同步完成的场景,直接设置结果不需要等待事件触发 if (!pending) tcs.TrySetResult(socketEventArg.SocketError); var error = await tcs.Task; return error == SocketError.Success; }
如果是.NET Core 2.1+/.NET 5+环境,官方已经提供了直接返回ValueTask<int>的ReceiveAsync重载,不需要自己维护SocketAsyncEventArgs对象,可以直接await,用法更简洁:
byte[] frameLenData = new byte[4]; int receivedLength = await _socket.ReceiveAsync(frameLenData, SocketFlags.None);
内容的提问来源于stack exchange,提问作者Sean
相关产品推荐
相关产品推荐

