0

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

4

2 回答 2

3

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

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

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

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

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

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

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

于 2020-10-10T21:54:46.370 回答
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");

代码示例:

这也与现有票证有关:

异步代码不比同步版本快

于 2021-10-06T05:42:47.510 回答