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

多线程生成PDF性能不升反降:基于ExpertPdf .NET库的技术求助

并行优化失效的原因及解决方案

核心问题分析

你的并行实现未达预期甚至耗时增加,主要源于以下几个关键问题:

1. 任务列表未清空,导致任务过载

原代码中tasks列表未在每批处理后清空,每一轮循环都会往列表中新增任务,后续Task.WhenAll需要等待的任务量越来越大。随着任务堆积,线程池被占满,大量任务排队等待执行,上下文切换开销飙升,最终效率反而低于串行执行。

2. IIS线程池资源冲突

你通过Task.Run占用ASP.NET线程池线程,同时又通过PdfConverter.GetPdfBytesFromUrl调用本地.aspx页面,这会再次占用IIS工作线程处理.aspx请求。ASMX/WCF与.aspx共享同一个IIS应用池,线程池最大线程数有限,大量并行请求会导致线程池耗尽,所有任务陷入等待空闲线程的状态,整体吞吐量急剧下降。

3. 不必要的HTTP调用开销

通过HTTP请求调用本地.aspx页面,额外增加了请求封装、IIS管道处理、响应解析的开销。单条请求的开销看似微小,但23万条累加后会造成巨大浪费;并行调用时,HTTP连接池也会成为新的瓶颈。

4. .aspx页面的串行瓶颈

如果.aspx页面的PageLoad逻辑存在共享资源竞争(比如单例数据库上下文、模板替换锁),并行调用时会出现排队等待,导致并行度无法提升,耗时反而增加。

针对性优化方案

1. 修复批量任务逻辑

每批处理后清空任务列表,确保Task.WhenAll仅等待当前批次的任务:

int batchSize = 3;
int numRegistro = request.Count;
var numberOfBatches = (int)Math.Ceiling((decimal)numRegistro / batchSize);

for (int i = 0; i < numberOfBatches; i++)
{
    var currentBatch = request.Skip(i * batchSize).Take(batchSize).ToList();
    var tasks = new List<Task<Response>>(); // 每批新建独立任务列表

    foreach (var row in currentBatch)
    {
        tasks.Add(Task.Run(() => ConvertHtmlToPdf(row.Request)));
    }

    var results = await Task.WhenAll(tasks).ConfigureAwait(false);
    // 处理当前批次结果
}

2. 移除不必要的HTTP调用

将.aspx页面的业务逻辑(数据库访问、模板替换)抽成独立类库方法,直接调用而非通过HTTP请求,省去额外开销:

// 抽离原.aspx的核心逻辑为独立异步方法
private async Task<byte[]> GeneratePdfContent(object request)
{
    // 原.aspx中的数据库查询(改为异步)
    var data = await GetDataFromDb(request.param1).ConfigureAwait(false);
    // 原.aspx中的模板标签替换
    string htmlContent = ReplaceTemplateTags(data);
    // 直接调用ExpertPdf API转换HTML为PDF
    return PdfConverter.GetPdfBytesFromHtmlString(htmlContent);
}

// 修改PDF生成方法为异步
private async Task<Response> ConvertHtmlToPdf(object request)
{
    var objResponse = new Response();
    objResponse.parambytes = await GeneratePdfContent(request).ConfigureAwait(false);
    return objResponse;
}

3. 控制并行度,避免资源耗尽

针对23万条记录的场景,根据服务器CPU核心数、数据库连接池大小合理设置并行度,避免任务过载:

// 例如基于CPU核心数设置并行上限,或参考数据库连接池最大连接数
int maxParallelism = Environment.ProcessorCount * 2;
var semaphore = new SemaphoreSlim(maxParallelism);

var tasks = request.Select(async row =>
{
    await semaphore.WaitAsync().ConfigureAwait(false);
    try
    {
        return await ConvertHtmlToPdf(row.Request).ConfigureAwait(false);
    }
    finally
    {
        semaphore.Release();
    }
}).ToList();

var allResults = await Task.WhenAll(tasks).ConfigureAwait(false);

4. 优化数据库访问逻辑

将单条记录查询改为批量查询,减少数据库连接开销;同时确保数据库连接池配置足够大,避免连接等待。例如每批查询当前批次的所有记录,而非逐条查询。

额外注意事项

  • 若使用.NET Framework的ASMX/WCF,确保异步方法正确实现(使用async/await,避免阻塞线程)。
  • 确认ExpertPdf库是否支持并行调用,若存在线程安全问题,需通过锁或单实例处理(但会降低并行度,建议更换线程安全的PDF生成库)。
  • 针对23万条超大量任务,可考虑引入消息队列(如RabbitMQ)做异步解耦,将PDF生成任务放入队列,由后台服务进程处理,避免占用Web服务资源。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 14:16:37