实现支持同步、异步Run方法的接口,有哪些最佳实践?
同步/异步Runner接口的最佳实践
针对你遇到的同步、异步Run方法共存,又不想每次检查对象类型的问题,以下是几种成熟的最佳实践:
1. 统一为异步接口(优先推荐)
直接定义一个只包含异步方法的接口,同步实现类通过Task.FromResult适配异步签名。这种方式让调用方无需区分类型,统一使用异步调用逻辑,代码最简洁。
public interface IRunner { Task<bool> RunAsync(); } // 同步逻辑实现类 public class SyncTaskRunner : IRunner { public Task<bool> RunAsync() { // 执行同步业务逻辑 bool result = ExecuteSyncOperation(); // 用Task.FromResult包装同步结果,无额外线程开销 return Task.FromResult(result); } private bool ExecuteSyncOperation() { // 实际同步操作 return true; } } // 异步逻辑实现类 public class AsyncTaskRunner : IRunner { public async Task<bool> RunAsync() { // 执行异步业务逻辑 return await ExecuteAsyncOperation(); } private Task<bool> ExecuteAsyncOperation() { // 实际异步操作(比如IO请求) return Task.Delay(100).ContinueWith(_ => true); } }
调用时直接遍历列表统一await即可:
var runners = new List<IRunner> { new SyncTaskRunner(), new AsyncTaskRunner() }; foreach (var runner in runners) { bool result = await runner.RunAsync(); // 处理结果 }
2. 带默认实现的单一接口(C# 8.0+)
利用C# 8.0的接口默认方法特性,让接口同时包含同步和异步方法,实现类只需要重写自己对应的逻辑,默认方法负责互相适配。
public interface IRunner { // 默认同步实现:调用异步方法并阻塞等待 bool Run() { // 使用GetAwaiter().GetResult()避免async/await带来的状态机开销 // 注意:在UI线程可能引发死锁,若有此类场景需让实现类自行重写Run方法 return RunAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } // 默认异步实现:调用同步方法并包装为Task Task<bool> RunAsync() { return Task.FromResult(Run()); } } // 同步实现类,仅重写Run方法 public class SyncRunner : IRunner { public bool Run() { // 同步业务逻辑 return true; } } // 异步实现类,仅重写RunAsync方法 public class AsyncRunner : IRunner { public async Task<bool> RunAsync() { // 异步业务逻辑 await Task.Delay(100); return true; } }
这种方式兼顾了同步和异步调用场景,但要注意默认适配逻辑的死锁风险,建议在UI或上下文敏感环境中,让同步实现类自行重写Run方法,避免依赖默认的阻塞逻辑。
3. 抽象基类适配(适合有共享逻辑场景)
如果你的Runner类需要共享一些通用状态或逻辑,可以用抽象基类替代接口,提供同步和异步的虚方法,子类选择实现对应逻辑,基类负责适配。
public abstract class RunnerBase { // 同步虚方法,子类可重写 public virtual bool Run() { return RunAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } // 异步虚方法,子类可重写 public virtual Task<bool> RunAsync() { return Task.FromResult(Run()); } // 共享的通用逻辑,比如日志、参数校验等 protected void CommonSetup() { // 通用初始化操作 } } public class SyncRunner : RunnerBase { public override bool Run() { CommonSetup(); // 同步业务逻辑 return true; } } public class AsyncRunner : RunnerBase { public override async Task<bool> RunAsync() { CommonSetup(); // 异步业务逻辑 await Task.Delay(100); return true; } }
这种方式的缺点是类只能继承一个抽象基类,灵活性不如接口,但适合有大量共享逻辑的场景。
避坑提示
- 不要用
Task.Run包装同步逻辑来实现异步接口,这会浪费线程池资源,同步逻辑直接用Task.FromResult返回即可。 - 在UI线程使用阻塞式等待(比如
GetResult())时,一定要用ConfigureAwait(false)避免死锁。
内容的提问来源于stack exchange,提问作者jaxjo
相关产品推荐
相关产品推荐

