【问题标题】: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】:

您的foreachawait 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
      相关资源
      最近更新 更多