Azure上ASP.NET Core用IronPDF 2021.3.1生成PDF遇文件占用冲突
问题描述
在Azure App Service托管的ASP.NET Core应用中使用IronPDF生成PDF,多用户并发场景下偶尔会抛出如下IOException:
The process cannot access the file 'C:\home\site\wwwroot\IronTemp\Temp{GUID}.tmp' because it is being used by another process.
已在GeneratePdf<T>方法中实现带指数退避的重试逻辑,但错误仍偶发。怀疑问题源于IronPDF依赖全局静态属性IronPdf.Installation.TempFolderPath存储临时文件,并发请求互相干扰导致文件冲突。尝试为每个请求创建唯一临时文件夹,但IronPDF不会自动使用该路径;考虑过用全局锁强制串行生成,但会严重影响并发性能。
核心问题:能否配置IronPDF为每个请求使用独立临时文件夹,或在Azure App Service并发环境中避免文件访问冲突?
额外信息
- 应用环境:Azure App Service上的ASP.NET Core
- IronPDF版本:2021.3.1
- 问题触发条件:偶发于高并发场景
附当前实现代码:
public async Task<byte[]> GeneratePdf<T>(ICollection<T> data, string template, string variablePrefix, PdfOptions pdfOptions, int maxRetries, int retryDelay) { if (data == null) throw new NullReferenceException("Kolekcja danych do wypełnienia nie może pozostać pusta!"); if (String.IsNullOrEmpty(variablePrefix)) throw new NullReferenceException("Prefix do zmiennej w szablonie nie może pozostać pusty!"); if (String.IsNullOrEmpty(template)) throw new NullReferenceException("Nie podano szablonu do wypełnienia!"); int delay = retryDelay; for (int attempt = 0; attempt < maxRetries; attempt++) { try { string filledTemplate = await FillTemplate(template, data, variablePrefix); IronPdf.HtmlToPdf renderer = new IronPdf.HtmlToPdf(); if (pdfOptions.PageLayout == PageLayout.Horizontal) renderer.PrintOptions.PaperOrientation = PdfPaperOrientation.Landscape; else renderer.PrintOptions.PaperOrientation = PdfPaperOrientation.Portrait; renderer.PrintOptions.FitToPaperWidth = true; renderer.PrintOptions.Zoom = 95; var pdf = renderer.RenderHtmlAsPdf(filledTemplate); return pdf.BinaryData; } catch (IOException ex) when (ex.Message.Contains("because it is being used by another process")) { if (attempt < maxRetries - 1) { Console.WriteLine($"Błąd IO, plik w użyciu. Próba {attempt + 1} z {maxRetries}. Opóźnienie {delay} ms."); await Task.Delay(delay); } else { throw new Exception("Nie udało się wygenerować pliku pdf po wielokrotnych próbach. Plik wciąż jest w użyciu.", ex); } } catch (Exception ex) { throw new Exception("Nie udało się wygenerować pliku pdf. Zgłoszono następujący błąd: " + ex.Message); } } return null; }
解决方案
1. 为每个请求配置独立临时文件夹(推荐)
IronPDF 2021.3.1支持通过IronPdf.Installation.TempFolderPath动态设置临时路径,结合请求级别的唯一文件夹创建,可避免并发冲突。关键是在每个请求的渲染周期内设置独立路径,并在请求结束后清理:
修改后的代码示例:
public async Task<byte[]> GeneratePdf<T>(ICollection<T> data, string template, string variablePrefix, PdfOptions pdfOptions, int maxRetries, int retryDelay) { if (data == null) throw new ArgumentNullException(nameof(data), "Kolekcja danych do wypełnienia nie może pozostać pusta!"); if (String.IsNullOrEmpty(variablePrefix)) throw new ArgumentNullException(nameof(variablePrefix), "Prefix do zmiennej w szablonie nie może pozostać pusty!"); if (String.IsNullOrEmpty(template)) throw new ArgumentNullException(nameof(template), "Nie podano szablonu do wypełnienia!"); int delay = retryDelay; string tempFolder = null; for (int attempt = 0; attempt < maxRetries; attempt++) { try { // 创建当前请求专属的临时文件夹 tempFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); Directory.CreateDirectory(tempFolder); // 绑定IronPDF使用该临时路径 IronPdf.Installation.TempFolderPath = tempFolder; string filledTemplate = await FillTemplate(template, data, variablePrefix); IronPdf.HtmlToPdf renderer = new IronPdf.HtmlToPdf(); if (pdfOptions.PageLayout == PageLayout.Horizontal) renderer.PrintOptions.PaperOrientation = PdfPaperOrientation.Landscape; else renderer.PrintOptions.PaperOrientation = PdfPaperOrientation.Portrait; renderer.PrintOptions.FitToPaperWidth = true; renderer.PrintOptions.Zoom = 95; var pdf = renderer.RenderHtmlAsPdf(filledTemplate); return pdf.BinaryData; } catch (IOException ex) when (ex.Message.Contains("because it is being used by another process")) { if (attempt < maxRetries - 1) { Console.WriteLine($"Błąd IO, plik w użyciu. Próba {attempt + 1} z {maxRetries}. Opóźnienie {delay} ms."); await Task.Delay(delay); delay *= 2; // 指数退避优化 } else { throw new Exception("Nie udało się wygenerować pliku pdf po wielokrotnych próbach. Plik wciąż jest w użyciu.", ex); } } catch (Exception ex) { throw new Exception("Nie udało się wygenerować pliku pdf. Zgłoszono następujący błąd: " + ex.Message, ex); } finally { // 清理临时文件夹,避免磁盘占用 if (tempFolder != null && Directory.Exists(tempFolder)) { try { Directory.Delete(tempFolder, recursive: true); } catch (Exception) { // 可记录日志,不抛出异常避免影响主流程 } } } } return null; }
原理:虽然
TempFolderPath是静态属性,但ASP.NET Core中每个请求在独立线程执行,设置路径后立即同步执行渲染,渲染完成后清理,不会和其他请求的配置互相干扰。
2. 升级IronPDF到最新版本
新版本IronPDF优化了临时文件命名规则,减少并发冲突概率,同时可能提供实例级别的临时路径配置(无需依赖全局静态属性),从根源降低冲突风险。
3. 代码优化建议
- 异常类型替换:将
NullReferenceException改为ArgumentNullException,符合.NET参数校验的规范。 - 异常包装:捕获非IO异常时,保留原始异常栈信息(如示例中添加
ex作为内部异常),便于排查问题。 - 指数退避:重试时延迟翻倍,提升高并发场景下的重试成功率。
内容的提问来源于stack exchange,提问作者wojas.

