如何基于返回Task的ReadAsync正确重写Stream.BeginRead方法
关于自定义Stream复用ReadAsync实现BeginRead的解答
复用可行性
完全可以复用ReadAsync的代码实现BeginRead,这种方案合法且高效,不需要维护同步异步两套独立逻辑,能大幅降低编码和后续维护成本。
实现原理
BeginRead属于.NET传统APM异步编程模型接口,要求返回IAsyncResult类型;而ReadAsync是TAP基于任务的异步模式实现,返回Task<int>类型。.NET的Task本身已经实现了IAsyncResult接口,只需要做简单的适配即可满足APM的接口要求。
通用实现代码
BeginRead实现
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { // 创建携带自定义状态的TaskCompletionSource var tcs = new TaskCompletionSource<int>(state); // 调用你已经实现的ReadAsync逻辑 ReadAsync(buffer, offset, count, CancellationToken.None).ContinueWith(task => { // 同步ReadAsync的执行结果 if (task.IsFaulted) { tcs.TrySetException(task.Exception.InnerExceptions); } else if (task.IsCanceled) { tcs.TrySetCanceled(); } else { tcs.TrySetResult(task.Result); } // 触发APM模式要求的回调 callback?.Invoke(tcs.Task); }, TaskContinuationOptions.ExecuteSynchronously); return tcs.Task; }
对应EndRead实现
public override int EndRead(IAsyncResult asyncResult) { if (asyncResult is not Task<int> readTask) { throw new ArgumentException("传入的IAsyncResult不是当前Stream生成的有效实例", nameof(asyncResult)); } // 正确抛出原始异常,不会封装AggregateException return readTask.GetAwaiter().GetResult(); }
注意事项
- 标准
Stream.BeginRead接口没有提供取消参数,所以调用ReadAsync时默认传入CancellationToken.None即可,无需额外处理取消逻辑 - 代码中使用
TaskContinuationOptions.ExecuteSynchronously可以避免不必要的线程调度,提升运行性能 EndRead中使用GetAwaiter().GetResult()而非直接访问Result,可以保证抛出的异常和ReadAsync中抛出的原始异常一致,符合APM模式的异常约定
内容的提问来源于stack exchange,提问作者boot4life
相关产品推荐
相关产品推荐

