ASP MVC 5中调用Puppeteer Sharp二次生成PDF超时崩溃问题
问题根因
你的代码核心问题是每次调用ViewAsPdf方法都会重复执行Chromium下载校验、重新启动浏览器实例,频繁的进程创建销毁会快速耗尽系统内存、端口、句柄资源,即使每次调用关闭逻辑,也会因为Chromium进程退出延迟导致资源泄漏,最终出现后续请求加载超时、程序崩溃的问题。
核心优化点
- 移除每次请求都执行的
browserFetcher.DownloadAsync逻辑,Chromium下载只需要在应用启动时执行一次 - 将Chromium浏览器实例改为单例复用,避免每次请求都创建销毁进程
- 给页面跳转方法加上超时限制,避免异常场景下无限等待
- 补充Chromium启动参数,适配服务器运行环境,减少不必要的资源占用
优化后代码
using PuppeteerSharp; using System.Threading.Tasks; using System.Web.Hosting; using System; namespace System.Web.Mvc { public static class MvcPdfExtensions { // 单例复用浏览器实例 private static readonly Lazy<Task<IBrowser>> _browserLazy = new Lazy<Task<IBrowser>>(InitBrowserAsync); private static async Task<IBrowser> InitBrowserAsync() { var hostPath = HostingEnvironment.MapPath("~/Content/"); var browserFetcher = new BrowserFetcher(new BrowserFetcherOptions() { Path = hostPath }); // 仅初始化时下载一次Chromium var revisionInfo = await browserFetcher.DownloadAsync(BrowserFetcher.DefaultChromiumRevision); return await Puppeteer.LaunchAsync(new LaunchOptions { Headless = true, // 服务器环境建议开启无头模式减少资源占用 ExecutablePath = revisionInfo.ExecutablePath, Args = new [] { "--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage" } // 适配服务器环境的启动参数 }); } public static async Task<ActionResult> ViewAsPdf(this Controller controller, string fileName, string actionName, string controllerName, object routeValues = null) { // 复用全局浏览器实例 var browser = await _browserLazy.Value; var page = await browser.NewPageAsync(); var cookies = controller.Request.Cookies.AllKeys; foreach (var cookieKey in cookies) { var cookie = controller.Request.Cookies[cookieKey]; await page.SetCookieAsync(new CookieParam { Name = cookie.Name, Value = cookie.Value, Domain = controller.Request.Url.Host, // 不要带端口,避免Cookie设置失效导致页面重定向 Path = "/" }); } try { var url = controller.Url.Action(actionName, controllerName, routeValues, "http"); var waitOptions = new WaitUntilNavigation[] { WaitUntilNavigation.Load }; // 增加15秒超时,避免无限等待 await page.GoToAsync(url, 15000, waitOptions); var pdfOptions = new PdfOptions { PrintBackground = true }; var file = await page.PdfDataAsync(pdfOptions); return new FileContentResult(file, "application/pdf") { FileDownloadName = fileName }; } catch (Exception e) { return new ContentResult { Content = $"生成PDF错误:类型:{e.Source} 错误信息:{e.Message}" }; } finally { await page.DisposeAsync(); // 浏览器实例不要关闭,留着复用 } } // 应用停止时调用该方法释放浏览器资源,可在Global.asax的Application_End事件中调用 public static async Task DisposeBrowserAsync() { if (_browserLazy.IsValueCreated) { await (await _browserLazy.Value).CloseAsync(); } } } }
额外注意事项
- 如果是IIS部署环境,需要给应用程序池的标识开启
允许加载用户配置文件选项,否则Chromium启动会失败 - 静态单例浏览器实例会在应用池回收时自动释放,也可以主动在
Application_End事件中调用MvcPdfExtensions.DisposeBrowserAsync()手动释放 - 如果出现页面加载超时,先直接访问生成PDF的URL确认页面本身是否可以正常打开,排除业务代码本身的响应慢问题
内容的提问来源于stack exchange,提问作者David Ortega
相关产品推荐
相关产品推荐

