为何使用Task.WhenAll并行调用后返回值为null?
并行调用Task.WhenAll后返回null的排查与解决
原本串行调用两个异步方法可正常获取预期数据,改为Task.WhenAll并行调用后,所有方法均返回null。在Visual Studio中查看Task对象,属性显示Id=2356、Status=RanToCompletion、Method={null}、Result=""。
原串行代码
private async Task<Entity> MethodAsync() { ... var model1 = await client.Method1(Id1, cancellationToken); var model2 = await client.Method2(Id2, cancellationToken); ... }
并行调用代码(多种写法均无效)
private async Task<Entity> MethodAsync() { var model1Task = client.Method1(Id1, cancellationToken); var model2Task = client.Method2(Id2, cancellationToken); var task = Task.WhenAll(new Task[] { model1Task ,model2Task }); await task; //doesn't work //task.Wait(); //doesn't work //await Task.WhenAll(new Task[] { model1Task , model2Task }); //doesn't work //Task.WhenAll(new Task[] { model1Task, model2Task}); //doesn't work }
客户端方法示例(Method2逻辑类似)
public async Task<Model> Method1(Guid Id, CancellationToken cancellationToken) { HttpResponseMessage responseMessage = await client.GetAsync($"customEndPoint"); ResponseMessageSingle<Model> response = JsonSerializer.Deserialize<ResponseMessageSingle<Model>>( await responseMessage.Content.ReadAsStringAsync(cancellationToken), new JsonSerializerOptions(JsonSerializerDefaults.Web)); return response.result; } private class ResponseMessageSingle<T> { public bool success { get; set; } public string message { get; set; } public T result { get; set; } }
自定义授权拦截器(DelegatingHandler)
protected override async Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { some logic... request.SetBearerToken(TokenResponse.AccessToken); HttpResponseMessage response = await base.SendAsync(request, cancellationToken); return await Task.FromResult(response); }
问题排查与解决方案
1. 修复拦截器的线程安全与冗余代码
拦截器中return await Task.FromResult(response);属于冗余代码,base.SendAsync已返回Task<HttpResponseMessage>,直接返回response即可,多余的包装不会引发错误,但会增加不必要的开销。
更关键的是:如果TokenResponse是多个请求共享的状态对象,并行调用时可能存在线程安全问题——并发请求可能同时修改或读取该对象,导致部分请求的Bearer Token无效,最终返回的响应中result为null。
修复后的拦截器代码:
protected override async Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { // 确保TokenResponse的访问是线程安全的,比如使用lock或线程安全容器 lock (TokenResponse) { request.SetBearerToken(TokenResponse.AccessToken); } HttpResponseMessage response = await base.SendAsync(request, cancellationToken); return response; // 移除冗余的Task.FromResult包装 }
2. 完善客户端方法的请求与响应处理
原客户端方法存在两个问题:
GetAsync未传入cancellationToken,无法在并行场景下正确响应取消操作;- 未检查HTTP响应状态,若请求返回错误码(如401、500),反序列化后
response.result会直接为null,无任何错误提示。
修复后的客户端方法:
public async Task<Model> Method1(Guid Id, CancellationToken cancellationToken) { // 传入cancellationToken到GetAsync HttpResponseMessage responseMessage = await client.GetAsync($"customEndPoint", cancellationToken); // 检查响应是否成功,失败则抛出异常 responseMessage.EnsureSuccessStatusCode(); var responseContent = await responseMessage.Content.ReadAsStringAsync(cancellationToken); ResponseMessageSingle<Model> response = JsonSerializer.Deserialize<ResponseMessageSingle<Model>>( responseContent, new JsonSerializerOptions(JsonSerializerDefaults.Web)); // 检查业务响应是否成功 if (!response.success) { throw new InvalidOperationException($"请求失败:{response.message}"); } return response.result; }
3. 修正并行代码的写法
并行代码无需将任务包装为Task[],直接传入泛型Task即可,且需明确获取每个任务的结果:
private async Task<Entity> MethodAsync() { var model1Task = client.Method1(Id1, cancellationToken); var model2Task = client.Method2(Id2, cancellationToken); // 等待所有任务完成 await Task.WhenAll(model1Task, model2Task); // 获取任务结果(或直接await每个任务,效果一致) var model1 = model1Task.Result; var model2 = model2Task.Result; // 组装Entity并返回 ... }
4. 验证响应内容
若以上修复无效,可在客户端方法中输出responseContent的内容,确认返回的JSON中result字段是否确实为null——这能帮你快速定位是请求层面的问题(如Token无效、接口返回错误),还是反序列化层面的问题。
内容的提问来源于stack exchange,提问作者TryHard
相关产品推荐
相关产品推荐

