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

C#实现异步并发下载 并发数大于1时无实时输出问题求助

问题根因

  • 同步阻塞调用导致线程池饥饿:MakeJOB中调用GetAsync、ReadAsStringAsync时使用.Result强制同步等待,会占用线程池线程不释放,并发量提高后线程池来不及分配新线程,所有任务的输出就会卡到接近全部完成才集中打印。
  • 异步逻辑被强制同步执行:NewRequest接收的是同步委托Func<HttpClient, T>,把异步请求逻辑包装成同步代码放到Task.Run中执行,没有真正利用异步IO不阻塞线程的特性。
  • 重复自增索引的潜在bug:MainAsync的循环中pos变量每次循环自增2次,会导致GethandlerIndexed取到超出数组长度的索引,触发空引用异常。
  • HttpClient误用:每次请求都新建HttpClient实例,频繁创建销毁会导致系统端口资源被快速占用,请求延迟升高。

修复方案

  1. 改造NewRequest方法支持异步委托,修改方法签名和内部执行逻辑:
// 修改委托类型为支持异步的Func<HttpClient, Task<T>>
public async Task<T> NewRequest<T>(Func<HttpClient, Task<T>> Expression, int? MaxTimeout = 2000, string Id = null)
{
    await Locker.WaitAsync(MaxTimeout ?? 2000, TokenSource.Token);

    bool persistent = true;
    if (Id is null)
    {
        persistent = false;
        Id = string.Empty;
    }

    try
    {
        HttpClient client = await CreateClient(Id, persistent, TokenSource.Token);
        // 直接await异步委托,不需要套Task.Run
        T result = await Expression(client);

        if (persistent is false)
            client?.Dispose();

        return result;
    }
    finally
    {
        Locker.Release();
    }
}
  1. 重写MakeJOB方法,替换所有.Result为await,消除同步阻塞:
private static async Task<bool> MakeJOB(int pos)
{
    return await cli.NewRequest<bool>(async (HttpClient client)=>
    {
        try
        {
            HttpClientHandler handler = null;
            if (cli.handler != null)
                handler = cli.GethandlerIndexed(pos);
            var requestClient = new HttpClient(handler);
            cli.AssignDefaultHeaders(requestClient);

            using HttpResponseMessage response = await requestClient.GetAsync("https://api.my-ip.io/ip.txt");
            string ip = await response.Content.ReadAsStringAsync();
            Console.WriteLine(ip + " / " + Task.CurrentId);
            return true;
        }
        catch { return false; }
    });
}
  1. 修复索引自增错误,修改MainAsync中的循环逻辑:
static async Task MainAsync(string[] args)
{
    cli.SetConcurrentDownloads(concurrency);
    var t = new Task[concurrency];
    for (int i = 0; i < t.Length; i++)
        t[i] = MakeJOB(i);
    await Task.WhenAll(t);
}
  1. 优化HttpClient复用:提前根据代理handler数组初始化对应HttpClient实例存储到Clients字典中,每次请求直接取出使用,不要每次新建实例。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 06:09:04