使用 async/await 时如何加速 IronPdf

How to speed up IronPdf when using async/await

我正在尝试使一段代码 运行 更快。该代码已在使用 async/await。但是还是很慢。

所以我尝试更改我的 foreach 以使用新的 IAsyncEnumerable。但是我从中获得了 0 性能。它似乎按顺序 运行 代码。这让我感到惊讶。我认为 await foreach 会 运行 每次迭代都在它自己的线程中。

这是我加速代码的尝试。

var bag = new ConcurrentBag<IronPdf.PdfDocument>(); // probably don't need a ConcurrentBag
var foos = _dbContext.Foos;
await foreach (var fooPdf in GetImagePdfs(foos))
{
    bag.Add(fooPdf);
}

private async IAsyncEnumerable<IronPdf.PdfDocument> GetImagePdfs(IEnumerable<Foo> foos)
{
    foreach (var foo in foos)
    {
        var imagePdf = await GetImagePdf(foo);

        yield return imagePdf;
    }
}

private async Task<IronPdf.PdfDocument> GetImagePdf(Foo foo)
{
    using var imageStream = await _httpService.DownloadAsync(foo.Id);
    var imagePdf = await _pdfService.ImageToPdfAsync(imageStream);

    return imagePdf;
}

using IronPdf;
public class PdfService
{
    // this method is quite slow
    public async Task<PdfDocument> ImageToPdfAsync(Stream imageStream)
    {
        var imageDataURL = Util.ImageToDataUri(Image.FromStream(imageStream));
        var html = $@"<img style=""max-width: 100%; max-height: 70%;"" src=""{imageDataURL}"">";
        using var renderer = new HtmlToPdf(new PdfPrintOptions()
        {
            PaperSize = PdfPrintOptions.PdfPaperSize.A4,
        });
        return await renderer.RenderHtmlAsPdfAsync(html);
    }
}

我也试了Parallel.ForEach

Parallel.ForEach(foos, async foo =>
{
    var imagePdf = await GetImagePdf(foo);
    bag.Add(imagePdf);
});

但是我一直在阅读我不应该对其使用异步,所以不确定该怎么做。这样做时 IronPdf 库也会崩溃。

您的 foreachawait foreach 方法的问题是它们将 顺序执行 (即使它们利用了 异步和等待模式)。本质上,await 正是这样做的,等待。

关于 Parallel.ForEach 您的怀疑是正确的,它不适合 IO 绑定工作负载的异步方法。 Parallel.ForEach 接受一个 Action 委托并给一个异步 lambda 给一个 Action 实际上只是创建一个 async void 每个 task 运行 未被观察到(这有几个缺点)。

这里有很多方法可以采用,但最简单的方法是热启动每个任务,将它们投射到一个集合中,然后 await 全部完成。通过这种方式,您可以让 IO 绑定的工作负载卸载(松散使用的术语)到 IO 完成端口,从而允许任何潜在的线程返回到线程池,以便任务计划程序有效地重用,直到 IO 工作完成。

假设没有共享资源,只需将启动的任务投影到 IEnumerable<Task<PdfDocument>> 并使用 Task.WhenAll

Creates a task that will complete when all of the supplied tasks have completed.

var tasks = _dbContext.Foos.Select(x => GetImagePdfs(x))
var results = await Task.WhenAll(tasks);

在上面的场景中,当 Select 枚举 async 方法时 GetImagePdfs 每个任务都是热启动的,任务调度器负责调度线程池中需要的任何线程.一旦任何代码等待 IO 作业,操作系统就会进行回调,并且线程会返回到池中以供重用,依此类推。 Task.WhenAll 等待所有任务完成或出错,然后 returns 每个结果的集合。

迁移到 IronPdf 2021.9 或更高版本显着改进了多线程支持并消除了我的应用程序中的死锁。
这影响了 IronPDF 的异步性能“html 到 pdf”PDF 渲染对于我的应用程序来说是可测量的:

https://www.nuget.org/packages/IronPdf/

// PM> Install-Package IronPdf
using IronPdf;
 
var Renderer = new IronPdf.ChromePdfRenderer();
 
// All IronPdf Rendering methods have Async equivalents
var doc = await Renderer.RenderHtmlAsPdfAsync("<h1>Html with CSS and Images</h1>");

doc.SaveAs("example.pdf");

代码示例:

这也与现有工单相关: