Skip to content

Latest commit

 

History

History
135 lines (99 loc) · 3.26 KB

File metadata and controls

135 lines (99 loc) · 3.26 KB

同步方法调用异步实现:最佳实践指南

问题背景

在同步方法中需要调用异步方法时,如何选择正确的实现方式?

三种实现方式对比

❌ 方式1: Fire-and-forget (不推荐)

_ = ResetAsync();

问题:

  • ❌ 不等待操作完成,方法立即返回
  • ❌ 异步操作在后台继续,可能导致资源泄漏
  • ❌ 异常被忽略,无法处理错误
  • ❌ 不是真正的同步实现

适用场景: 仅用于不需要等待且不关心结果的场景(如日志记录)


❌ 方式2: Task.Run (不推荐)

Task.Run(() => ResetAsync()).GetAwaiter().GetResult();

问题:

  • ❌ 创建额外的不必要任务
  • ❌ 性能开销(多线程切换)
  • ❌ 代码冗余
  • ❌ 可能引入线程池饥饿

适用场景: 仅在需要强制在新线程运行且不能使用ConfigureAwait时


✅ 方式3: ConfigureAwait(false) (推荐)

ResetAsync().ConfigureAwait(false).GetAwaiter().GetResult();

优点:

  • ✅ 直接等待异步操作完成
  • ConfigureAwait(false) 避免死锁
  • ✅ 性能最优(无额外任务创建)
  • ✅ 代码简洁清晰
  • ✅ C#异步编程标准实践

技术细节:

  • ConfigureAwait(false): 告诉任务不需要捕获当前的SynchronizationContext
  • GetAwaiter().GetResult(): 同步等待结果并获取值
  • 避免UI线程死锁:UI线程不会尝试回到原始上下文

适用场景: 在同步方法中需要调用异步方法的正确做法


🏆 最佳实践总结

1. 优先使用 ConfigureAwait(false)

public void SyncMethod() {
    AsyncMethod().ConfigureAwait(false).GetAwaiter().GetResult();
}

2. 避免 Fire-and-forget

除非真的不需要等待结果,且不关心异常。

3. 谨慎使用 Task.Run

仅在需要隔离线程或避免上下文捕获问题时使用。


实际案例:IoBoxTypeSetterSelector.Reset()

修复前的问题

// 可能死锁 - 没有ConfigureAwait
ResetAsync().GetAwaiter().GetResult();

修复后的实现

public virtual void Reset() {
    try {
        ResetAsync().ConfigureAwait(false).GetAwaiter().GetResult();
    } catch (AggregateException ex) {
        throw ex.InnerException ?? ex;
    } catch (Exception) {
        throw;
    }
}

关键改进

  1. ✅ 添加 ConfigureAwait(false) 避免UI线程死锁
  2. ✅ 完善异常处理
  3. ✅ 保持向后兼容性
  4. ✅ 性能优化

📚 参考资料


🎯 结论

在同步方法中调用异步方法时:

首选: AsyncMethod().ConfigureAwait(false).GetAwaiter().GetResult()

原因:

  • 性能最佳
  • 避免死锁
  • 代码简洁
  • 行业标准实践

何时使用Task.Run:

  • 需要强制隔离线程
  • 无法修改异步方法调用时
  • 需要特殊的线程处理逻辑

文档版本: v1.0 创建日期: 2025-12-05 适用范围: C# .NET 异步编程