You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 08:45:49