如何优化C#中依赖GetMyData的OData扩展操作以降低延迟?
优化方案:并行主数据与扩展数据查询
核心优化思路是将主数据查询(GetMyData)与扩展数据查询并行执行——原流程是串行执行主查询+并行扩展查询,总耗时为主查询时间 + 最长扩展查询时间;优化后改为并行执行两类查询,总耗时缩短为max(主查询时间, 最长扩展查询时间),直接降低整体延迟。
具体实现步骤
- 拆分扩展操作:把
AppendExpandOption1DataAsync/AppendExpandOption2DataAsync拆分为「数据查询」和「结果填充」两个独立部分,让查询逻辑可以提前启动,无需等待主数据转换完成。 - 并行启动任务:在拿到主查询所需的
identifierId/identifierName后,同时启动主数据查询和对应扩展项的数据查询任务。 - 错误关联处理:用
CancellationToken关联主查询与扩展查询,主查询失败时自动取消扩展查询,避免无效数据库调用。
优化后代码示例
public async Task<ResponseType> GetDataAsync( RequestType request, ODataQueryOptions<ResponseType>? options = null, string? version = null, CancellationToken cancellationToken = default) { if (request == null) throw new ArgumentNullException(nameof(request)); // 提前解析OData扩展选项 IEnumerable<ExpandedNavigationSelectItem>? expandOptions = options?.SelectExpand?.SelectExpandClause.SelectedItems.OfType<ExpandedNavigationSelectItem>(); (string identifierName, string identifierId) = Utils.GetIdentifier(request); // 并行启动主数据查询和扩展数据查询任务 var mainDataTask = dataApiClient.GetMyData(identifierId, identifierName, version, cancellationToken); var expandQueryTasks = new List<Task>(); var expandFillOperations = new List<Action<ResponseType>>(); foreach (var item in expandOptions ?? Enumerable.Empty<ExpandedNavigationSelectItem>()) { var segmentName = item.PathToNavigationProperty.FirstSegment.Identifier; if (segmentName.Equals("ExpandOption1", StringComparison.CurrentCultureIgnoreCase)) { // 先启动扩展1的数据查询,查询完成后暂存结果,后续填充到response ExpandOption1Type? option1Data = null; expandQueryTasks.Add(GetExpandOption1DataAsync(identifierId, identifierName, version, cancellationToken) .ContinueWith(t => option1Data = t.Result, cancellationToken)); expandFillOperations.Add(response => response.ExpandOption1 = option1Data); } else if (segmentName.Equals("ExpandOption2", StringComparison.CurrentCultureIgnoreCase)) { ExpandOption2Type? option2Data = null; expandQueryTasks.Add(GetExpandOption2DataAsync(identifierId, identifierName, version, cancellationToken) .ContinueWith(t => option2Data = t.Result, cancellationToken)); expandFillOperations.Add(response => response.ExpandOption2 = option2Data); } } // 等待主数据查询完成(若主查询失败,直接抛出异常,扩展任务会被取消) DataPreview dataPreview = await mainDataTask; ResponseType response = dataPreview.ConvertToResponse(); // 等待所有扩展查询完成,再将结果填充到response if (expandQueryTasks.Count > 0) { await Task.WhenAll(expandQueryTasks); foreach (var fillOp in expandFillOperations) { fillOp(response); } } return response; } // 拆分后的扩展数据查询方法(仅负责数据库调用) private async Task<ExpandOption1Type> GetExpandOption1DataAsync(string identifierId, string identifierName, string? version, CancellationToken cancellationToken) { // 此处替换为原AppendExpandOption1DataAsync中的数据库查询逻辑 return await dataApiClient.FetchExpandOption1Data(identifierId, identifierName, version, cancellationToken); } // 拆分后的扩展数据查询方法(Option2同理) private async Task<ExpandOption2Type> GetExpandOption2DataAsync(string identifierId, string identifierName, string? version, CancellationToken cancellationToken) { return await dataApiClient.FetchExpandOption2Data(identifierId, identifierName, version, cancellationToken); }
额外注意事项
- 无法拆分扩展方法的兼容方案:如果不能修改
AppendExpandOptionXDataAsync(比如依赖外部组件),可以尝试和数据API团队沟通,提供批量获取主数据+扩展数据的接口,从根源减少数据库调用次数;不建议提前初始化空ResponseType占位,容易引入未初始化的空引用风险。 - 异常处理:原代码中
GetMyData可能返回500/404,优化后需确保主查询抛出异常时,扩展任务能被及时取消,避免无效的资源占用。 - 代码可读性:替换原代码中的
List.ForEach为foreach循环,便于调试和维护。
内容的提问来源于stack exchange,提问作者Programmerzzz
相关产品推荐
相关产品推荐

