.NET Framework 4.8异步任务批量执行仅部分完成问题求助
在.NET Framework 4.8环境下,创建75个Task调用第三方分页API,返回类型为Task<List<Dictionary<string, object>>>。初始因闭包特性导致分页参数错误,但所有任务均能执行完成;改用迭代器赋值新变量、Task.Factory.StartNew传状态对象解决闭包问题后,仅少量任务成功,多数失败。错误包含TaskCanceledException、请求被取消的WebException,AggregateException提供的有效信息极少,需排查原因。
相关代码
调用函数代码
public async Task<List<Dictionary<string, object>>> ExecuteAsyncJSON( string requestSource = "fields", string searchCount = "", Object iteration = null) { string url = requestSource; // 添加分页参数 if (searchCount != "" && iteration != null) { // 按500条分页计算 int quotient = (Int32.Parse(searchCount) / 500); int remainder = Int32.Parse(searchCount) % 500; int i = Convert.ToInt32(iteration); int add = i == quotient ? remainder : 500; int start = i * 500; int end = (i * 500) + add; url = url.Contains("?") ? $"{url}&start={start}&end={end}" : $"{url}?start={start}&end={end}"; } try { // 调用API using (HttpResponseMessage response = await someclient.GetAsync(url)) { if (response.IsSuccessStatusCode) { var serializer = new JavaScriptSerializer(); var responseBodyAsText = await response.Content.ReadAsStringAsync(); return serializer.Deserialize<List<Dictionary<string, object>>>(responseBodyAsText); } else { throw new Exception(response.ReasonPhrase); } } } catch { throw new Exception(); } }
主执行代码
var requestSource = "searches/" + searchId + "/members"; var searchCount = "37301"; int quotient = (Int32.Parse(searchCount) / 500); var tasks = new List<Task<List<Dictionary<string, object>>>>(); // 批量创建任务,通过StartNew传递迭代器状态 for (var i = 0; i <= quotient; i++) { var task = Task.Factory.StartNew((iteration) => { return ExecuteAsyncJSON(requestSource, searchCount, iteration); }, i) .Unwrap(); tasks.Add(task); } try { Task.WaitAll(tasks.ToArray()); } catch (AggregateException ae) { // 暂时注释抛出异常 } // 收集结果 List<Dictionary<string, object>> completedResults = new List<Dictionary<string, object>>(); List<Task<List<Dictionary<string, object>>>> faultedResults = new List<Task<List<Dictionary<string, object>>>>(); foreach (var listTask in tasks) { if (listTask.Status == TaskStatus.RanToCompletion) completedResults.AddRange(listTask.Result); if (listTask.Status == TaskStatus.Faulted) faultedResults.Add(listTask); }
调试数据
WaitAll抛出的AggregateException
发生一个或多个错误。 引发了类型为“System.Exception”的异常。 在 System.Threading.Tasks.Task.WaitAll(Task[] tasks, Int32 millisecondsTimeout, CancellationToken cancellationToken) 在 System.Threading.Tasks.Task.WaitAll(Task[] tasks, Int32 millisecondsTimeout) 在 System.Threading.Tasks.Task.WaitAll(Task[] tasks) 在 Smu.Bbec.Emma.API.EmmaAPI.<GetMembersMatchingSearch>d__22.MoveNext() 中 [指向Task.WaitAll(tasks.ToArray());]
失败任务的内部异常
76次调用仅4次成功,72次失败的异常:
System.Exception: 引发了类型为“System.Exception”的异常。 在 Smu.Bbec.Emma.API.EmmaAPI.<ExecuteAsyncJSON>d__8.MoveNext() 中 [指向实际调用API的代码]
断点捕获的错误
任务已取消。 在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 在 System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() 在 Smu.Bbec.Emma.API.EmmaAPI.<ExecuteAsyncJSON>d__8.MoveNext() 中 f:\TestApps\Smu.Bbec.Emma\Smu.Bbec.Emma.API\EmmaAPI.cs:第100行
另一种错误
发送请求时出错。 内部异常: 请求已中止: 请求已取消 (System.Exception {System.Net.WebException})
取消操作的完整调试数据(敏感信息已移除)
$exception {"任务已取消。"} System.Threading.Tasks.TaskCanceledException this {**********.EmmaAPI} **********.EmmaAPI _accountId "********" string _authorization "MTg4NzblahblahblahblahmM6MDNmYzU1blahblahblahblahjM=" string _baseUri {https://api.e2ma.net/********/} System.Uri _baseUrl "https://api.e2ma.net/********/" string _publicKey "********" string _secretKey "********" string emmaClient {System.Net.Http.HttpClient} System.Net.Http.HttpClient BaseAddress {https://api.e2ma.net/********/} System.Uri DefaultRequestHeaders {Accept: application/json Authorization: Basic MblahblahblahblahTFjYmblahblahblahblahM4M2IyMjM=} System.Net.Http.Headers.HttpRequestHeaders MaxResponseContentBufferSize 2147483647 long Timeout {00:01:40} System.TimeSpan baseAddress {https://api.e2ma.net/********/} System.Uri defaultRequestHeaders {Accept: application/json Authorization: Basic MTgblahblahblahblah4Y2blahblahblahblahNmYzblahblahblahblahjM=} System.Net.Http.Headers.HttpRequestHeaders disposeHandler true bool disposed (System.Net.Http.HttpMessageInvoker) false bool disposed false bool handler {System.Net.Http.HttpClientHandler} System.Net.Http.HttpMessageHandler {System.Net.HttpClientHandler} maxResponseContentBufferSize 2147483647 long operationStarted true bool pendingRequestsCts {System.Threading.CancellationTokenSource} System.Threading.CancellationTokenSource timeout {00:01:40} System.TimeSpan Static members requestSource "searches/17297143/members" string searchCount "37810" string iteration 67 object {int} url "searches/17297143/members?start=33500&end=34000" string
1. 修复异常吞吃问题,获取真实错误信息
当前ExecuteAsyncJSON的catch块直接抛出空的Exception,完全丢失原始错误细节,这是排查最大阻碍:
catch (Exception ex) { // 记录日志,包含url、迭代器参数和原始异常信息 throw new Exception($"请求失败,URL:{url}", ex); // 将原始异常作为内部异常抛出 }
修改后AggregateException会展示真实错误原因,比如API限流、超时、参数错误等。
2. 调整HttpClient的连接数限制
.NET Framework中HttpClientHandler默认MaxConnectionsPerServer为2,同时发起75个请求会导致大量请求排队超时被取消。创建someclient时配置连接数:
var handler = new HttpClientHandler { MaxConnectionsPerServer = 15 // 根据API承受能力调整,建议10-20 }; someclient = new HttpClient(handler) { BaseAddress = new Uri(_baseUrl), Timeout = TimeSpan.FromMinutes(5) // 适当延长超时时间 };
3. 避免同步阻塞异步代码
使用Task.WaitAll同步等待大量异步任务,可能引发线程池饥饿或上下文死锁,建议改用异步等待:
// 替换Task.WaitAll await Task.WhenAll(tasks);
若必须同步等待,可包裹在Task.Run中执行,但优先推荐全异步模式。
4. 实现请求限流,适配第三方API规则
多数第三方API会限制并发请求数,超过限制会直接取消请求。用SemaphoreSlim控制并发数:
var semaphore = new SemaphoreSlim(10); // 限制同时10个请求 var tasks = new List<Task<List<Dictionary<string, object>>>>(); for (var i = 0; i <= quotient; i++) { var iteration = i; tasks.Add(Task.Run(async () => { await semaphore.WaitAsync(); try { return await ExecuteAsyncJSON(requestSource, searchCount, iteration); } finally { semaphore.Release(); } })); }
5. 验证分页参数正确性
确认分页参数符合API要求:
- 检查
end参数是否为start+add-1(部分API采用左闭右开区间); - 打印所有任务的
url,确认参数无重复或超出范围。
6. 调整超时设置
当前HttpClient超时为1分40秒,若API响应慢或请求排队时间过长,会触发超时取消。可适当延长超时时间,同时监控单个请求的响应耗时。
内容的提问来源于stack exchange,提问作者WillemV

