【问题标题】:How to speed up IronPdf when using async/await使用 async/await 时如何加速 IronPdf
【发布时间】:2020-10-10 18:32:49
【问题描述】:

我正在尝试让一段代码运行得更快。代码已经在使用 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 库也会崩溃。

【问题讨论】:

  • “它似乎是按顺序运行代码”愿意分享是什么让您得出这个结论?
  • 我在其中放置了一些日志消息以打印它何时开始获取 foo.Id 以及何时将其添加到包中。顺序是:开始 1,完成 1,开始 2,完成 2,等等
  • var tasks = _dbContext.Foos.Select(x =&gt; GetImagePdfs(x)); await Task.WhenAll(Tasks);
  • @MichaelRandall 感谢它的工作原理。我预计await foreach 会这样工作,但显然误解了它的用途
  • 作为旁注,如果我听起来很迂腐,很抱歉,async/await 与使事情运行得更快没有直接关系。如果您有许多操作要做并且想要更快地完成,解决方案是同时运行,而不是异步。单独从同步切换到异步几乎不会产生任何影响。从顺序切换到并发将改变世界。这是一个相关的问题:What is the difference between concurrency, parallelism and asynchronous methods?

标签: c# async-await parallel-processing ironpdf


【解决方案1】:

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

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

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

假设没有共享资源,只需将启动的任务投射到IEnumerable&lt;Task&lt;PdfDocument&gt;&gt; 并使用Task.WhenAll

创建一个将在所有提供的任务都完成时完成的任务 完成。

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

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

【讨论】:

    【解决方案2】:

    迁移到 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");
    
    

    代码示例:

    这也与现有票证有关:

    Asynchronous Code Is Not Faster than the Synchronous Version

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-08-07
      • 1970-01-01
      • 2020-08-01
      • 2020-08-23
      • 1970-01-01
      • 2016-09-03
      • 2011-05-04
      • 2018-12-26
      相关资源
      最近更新 更多