在for循环中使用await调用MS Graph转PDF接口的异步优化问题
问题描述
我通过MS Graph调用转PDF接口,将生成的base64编码字符串返回给前端供pdf.js处理。为提升处理速度,我想用async/await实现尽可能多的接口并行调用(不使用MS Graph的批量功能),但不确定当前代码是串行等待每个await结果还是能循环发起请求。我需要的是循环发起所有请求,待全部完成后再返回最终结果。
当前代码
public async Task<ActionResult> PDFVersionsToBase64([FromForm] string ID) { //Using FormData on frontend //checking ID exists on searching in dashboard if (String.IsNullOrEmpty(ID)) { return Ok(new { Result = "Failed" }); } else { String query = "select * from dbo.function(@ID)"; using (var connection = new SqlConnection(connectionString)) { var documents = connection.Query<QuestionDocuments>(query, new { ID = ID}); foreach (var file in documents) { var fullPath = Path.Combine(fileDirectory, file.path); using (var memoryStream = await oneDrive.DriveItemConvertToPdfAsync(SharedDriveID, fullPath, "path")) { byte[] buffer = new byte[memoryStream.Length]; memoryStream.Read(buffer, 0, (int)memoryStream.Length); file.Base64 = Convert.ToBase64String(buffer); } } return Ok(documents); } } } public async Task<Stream> DriveItemConvertToPdfAsync(string DriveID, string PathOrDriveItemID, string SearchType) { Stream response = null; try { var queryOptions = new List<QueryOption>() { new QueryOption("format", "pdf") }; if (SearchType == "path") { response= await _graphServiceClient.Me.Drives[DriveID].Root .ItemWithPath(PathOrDriveItemID) .Content .Request(queryOptions) .GetAsync(); } else if (SearchType == "id") { response = await _graphServiceClient.Me.Drives[DriveID].Items[PathOrDriveItemID] .Content .Request(queryOptions) .GetAsync(); } } catch (ServiceException ex) { Console.WriteLine($"Error deleting file: {ex.ToString()}"); } return response; }
当前代码的问题
你现在的foreach循环中每次都直接await异步方法,这会导致串行执行:必须等当前文件的转PDF请求完全完成,才会发起下一个文件的请求,完全没有利用异步并行的优势。
并行改造方案
要实现并行,核心是先批量创建所有异步任务(发起请求),再统一等待所有任务完成。具体改造如下:
改造后的代码
public async Task<ActionResult> PDFVersionsToBase64([FromForm] string ID) { // 使用前端FormData // 检查ID有效性 if (String.IsNullOrEmpty(ID)) { return Ok(new { Result = "Failed" }); } string query = "select * from dbo.function(@ID)"; using (var connection = new SqlConnection(connectionString)) { // 转为List方便后续操作 var documents = connection.Query<QuestionDocuments>(query, new { ID = ID}).ToList(); // 批量创建异步任务,此时所有请求已经开始发起 var tasks = documents.Select(async file => { var fullPath = Path.Combine(fileDirectory, file.path); using (var memoryStream = await oneDrive.DriveItemConvertToPdfAsync(SharedDriveID, fullPath, "path")) { if (memoryStream != null) { byte[] buffer = new byte[memoryStream.Length]; // 改用异步读取流,避免阻塞线程 await memoryStream.ReadAsync(buffer, 0, (int)memoryStream.Length); file.Base64 = Convert.ToBase64String(buffer); } else { // 标记转换失败,可根据需求自定义 file.Base64 = null; } } }).ToList(); // 等待所有异步任务全部完成 await Task.WhenAll(tasks); return Ok(documents); } } // 保留原方法,优化异常提示 public async Task<Stream> DriveItemConvertToPdfAsync(string DriveID, string PathOrDriveItemID, string SearchType) { try { var queryOptions = new List<QueryOption>() { new QueryOption("format", "pdf") }; if (SearchType == "path") { return await _graphServiceClient.Me.Drives[DriveID].Root .ItemWithPath(PathOrDriveItemID) .Content .Request(queryOptions) .GetAsync(); } else if (SearchType == "id") { return await _graphServiceClient.Me.Drives[DriveID].Items[PathOrDriveItemID] .Content .Request(queryOptions) .GetAsync(); } } catch (ServiceException ex) { Console.WriteLine($"转换PDF出错: {ex.ToString()}"); } return null; }
关键说明
- 并行核心:
Select(async file => ...)会为每个文档生成一个Task对象,此时所有转PDF请求已经同时发起,而非等待上一个完成才开始 - 统一等待:
Task.WhenAll(tasks)会阻塞当前线程直到所有异步任务完成,确保所有文件都处理完毕后再返回结果 - 异步流读取:将
memoryStream.Read替换为await memoryStream.ReadAsync,避免线程阻塞,进一步提升异步效率 - 容错处理:单个文件转换失败不会导致整个请求失败,可通过
file.Base64的状态标记转换结果,后续可根据需求调整失败逻辑
内容的提问来源于stack exchange,提问作者Qiuzman
相关产品推荐
相关产品推荐

