通过异步版本实现非异步方法的DRY是否合理?
使用GetAwaiter().GetResult()同步调用异步方法是否妥当?
为了遵循DRY原则,我尝试用异步版本的方法来实现同步方法,写法是调用异步方法后用GetAwaiter().GetResult()获取结果。虽然代码能正常运行,但这种做法是否存在问题?如果有,原因是什么?
原始代码
internal static ISector Read(ISector sector, Stream stream) { using var scope = new ArrayPoolScope<byte>(GetLength(sector)); stream.ReadExactly(scope.Span); var result = Read(sector, scope.Span); return result; } internal static async Task<ISector> ReadAsync(ISector sector, Stream stream) { using var scope = new ArrayPoolScope<byte>(GetLength(sector)); await stream.ReadExactlyAsync(scope.Memory); var result = Read(sector, scope.Span); return result; }
修改后代码
internal static ISector Read(ISector sector, Stream stream) { var result = ReadAsync(sector, stream).GetAwaiter().GetResult(); return result; } internal static async Task<ISector> ReadAsync(ISector sector, Stream stream) { using var scope = new ArrayPoolScope<byte>(GetLength(sector)); await stream.ReadExactlyAsync(scope.Memory); var result = Read(sector, scope.Span); return result; }
这种做法非常不妥,核心问题包括:
- 死锁风险极高:在存在同步上下文的环境(如WinForms、WPF、旧版ASP.NET)中,异步方法执行
await后会试图回到原同步上下文继续执行,但此时调用GetAwaiter().GetResult()的线程正占用着该上下文等待结果,双方互相阻塞,直接导致死锁。 - 浪费线程池资源:异步设计的核心是高效利用线程池,避免线程被阻塞在IO操作上。而同步调用异步方法会把线程池线程长时间占住,直到异步IO完成,严重降低系统的并发处理能力,高负载场景下影响尤为明显。
- 异常处理混乱:
GetAwaiter().GetResult()会直接抛出原始异常,但异步流程中的异常栈可能被破坏,导致排查问题时难以定位根源;相比之下,正常的异步异常处理能保留更清晰的调用栈信息。 - 上下文丢失或错误流转:部分异步操作依赖特定上下文(如请求上下文、安全上下文),同步调用时可能导致上下文无法正确传递,引发意料之外的逻辑错误。
更合理的DRY实现方式
把公共的业务逻辑抽成独立的同步方法,让同步和异步方法各自处理IO部分,再调用公共逻辑:
// 抽离公共核心逻辑 private static ISector ReadCore(ISector sector, ReadOnlySpan<byte> data) { // 这里放置原来Read(sector, scope.Span)的具体逻辑 return result; } internal static ISector Read(ISector sector, Stream stream) { using var scope = new ArrayPoolScope<byte>(GetLength(sector)); stream.ReadExactly(scope.Span); return ReadCore(sector, scope.Span); } internal static async Task<ISector> ReadAsync(ISector sector, Stream stream) { using var scope = new ArrayPoolScope<byte>(GetLength(sector)); await stream.ReadExactlyAsync(scope.Memory); return ReadCore(sector, scope.Span); }
这样既实现了代码复用,又完全避免了同步调用异步方法带来的各种风险。
内容的提问来源于stack exchange,提问作者aybe
相关产品推荐
相关产品推荐

