用Task.ContinueWith替代try/catch实现多数据源异步容错逻辑
解决方案:多数据源顺序回退的异步实现
问题分析
你尝试的ContinueWith版本存在两个核心问题:
- 当缓存获取成功时,带
TaskContinuationOptions.OnlyOnFaulted的延续任务会进入取消状态,调用.Result会抛出未处理的异常,导致客户端无法获取正常结果。 - 直接使用
.Result会阻塞线程,违背异步设计初衷,还可能引发死锁;同时ContinueWith的嵌套写法在扩展3个及以上数据源时,可读性和维护性依然很差。
推荐方案:通用顺序回退方法
最简洁且易维护的方式是封装一个通用方法,按优先级逐个尝试数据源,直到获取成功结果。这种方式扩展新数据源只需要添加方法到列表即可,完全避免嵌套try/catch。
1. 封装通用尝试方法
private async Task<T> TryFirstSuccessAsync<T>(IEnumerable<Func<Task<T>>> dataSources) { foreach (var source in dataSources) { try { return await source(); } catch { // 忽略当前数据源异常,尝试下一个 continue; } } // 所有数据源失败时抛出统一异常 throw new InvalidOperationException("所有数据源均无法获取数据"); }
2. 改造你的仓储类
public class CourseRepository { private List<Course> _cache = new(); private async Task<List<Course>> GetCoursesCache() { if (_cache.Count == 0) throw new InvalidOperationException("缓存为空"); return _cache; } private async Task<List<Course>> GetCoursesApi() { var responses = await callApi(); // 确保调用异步API时使用await if (responses != null) _cache.AddRange(responses); return responses; } // 可新增本地数据库数据源 // private async Task<List<Course>> GetCoursesLocalDb() // { // // 从本地DB读取逻辑 // } public async Task<List<Course>> GetCourses() { var dataSources = new List<Func<Task<List<Course>>>> { GetCoursesCache, // 优先级最高:先尝试缓存 GetCoursesApi, // 缓存失败尝试API // GetCoursesLocalDb, // 可继续添加更多数据源 }; return await TryFirstSuccessAsync(dataSources); } }
可选:修复后的ContinueWith实现(不推荐)
如果你坚持使用ContinueWith,需要正确处理任务成功/失败的分支,并解包嵌套任务:
public Task<List<Course>> GetCourses() { return GetCoursesCache() .ContinueWith(previousTask => { // 缓存失败时调用API,成功则直接返回缓存结果 return previousTask.IsFaulted ? GetCoursesApi() : Task.FromResult(previousTask.Result); }, TaskContinuationOptions.ExecuteSynchronously) .Unwrap(); // 解包Task<Task<List<Course>>>为Task<List<Course>> }
注意:这种写法在扩展多数据源时需要不断嵌套
ContinueWith,可读性远不如通用方法,仅作参考。
内容的提问来源于stack exchange,提问作者Clément Jean
相关产品推荐
相关产品推荐

