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

C#限制并发HTTP调用遇异常:完成提示提前+请求逐个启动

问题分析与解决方案

问题原因

1. 「Done」日志提前打印

你使用Thread.Join()等待线程完成,但传给Thread的是**async void委托**。异步方法执行到第一个await时会立即返回给线程,线程随即结束,但后续的信号量等待、HTTP请求等异步操作仍在后台执行。因此所有线程Join完成时,实际的HTTP请求可能还未全部结束,导致「Done」提前输出。

2. 后续请求无法并发

每个请求都创建新Thread,线程的创建与调度开销极大,且async void委托的调度逻辑存在缺陷:线程在await后退出,后续异步操作由线程池调度,但信号量的释放时机与线程生命周期不匹配,可能导致信号量无法及时释放,使得后续请求只能逐个获取信号量启动。

修正后的代码

var semaphore = new SemaphoreSlim(5, 5);
var tasks = new List<Task>();
var values = new ConcurrentBag<string>();

for (var i = 0; i < callsLeft; i++)
{
    var url = $"..."; // 替换为实际URL生成逻辑
    tasks.Add(FetchUrlAsync(url, semaphore, _client, cancellationToken));
}

await Task.WhenAll(tasks);
Debug.WriteLine("Done");

// 封装单个请求的异步逻辑
async Task FetchUrlAsync(string url, SemaphoreSlim semaphore, HttpClient client, CancellationToken cancellationToken)
{
    await semaphore.WaitAsync(cancellationToken);
    try
    {
        var content = await client.GetStringAsync(url, cancellationToken);
        values.Add(content);
    }
    finally
    {
        semaphore.Release(); // 确保信号量在任何情况下都能释放,避免死锁
    }
}

关键修改说明

  • 用Task替代Thread:异步任务由.NET线程池高效调度,避免创建大量线程的性能开销,同时能正确跟踪异步操作的完成状态。
  • Task.WhenAll等待所有任务:替代Thread.Join,确保所有HTTP请求执行完毕后才打印「Done」。
  • 避免async void:将异步逻辑封装为async Task方法,保证异常可捕获、操作状态可跟踪。
  • try/finally保证信号量释放:即使HTTP请求抛出异常,信号量也能被正确释放,防止后续请求因无法获取信号量而阻塞。

内容的提问来源于stack exchange,提问作者Gargoyle

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 00:40:27