如何解决异步Lambda函数中MemoryStream的已释放对象访问错误
错误现象
同步运行代码一切正常,改为异步并行执行后,调用自定义Azure下载方法DriveItemDownloadAsync时抛出以下错误:
System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'IServiceProvider'.at Microsoft.Extensions.DependencyInjection.ServiceLookup.ThrowHelper.ThrowObjectDisposedException()
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider)
at Microsoft.Identity.Web.TokenAcquisitionAspnetCoreHost.GetEffectiveAuthenticationScheme(String authenticationScheme)
at Microsoft.Identity.Web.TokenAcquisitionAspnetCoreHost.GetOptions(String authenticationScheme, String& effectiveAuthenticationScheme)
at Microsoft.Identity.Web.TokenAcquisition.GetAuthenticationResultForUserAsync(IEnumerable1 scopes, String authenticationScheme, String tenantId, String userFlow, ClaimsPrincipal user, TokenAcquisitionOptions tokenAcquisitionOptions)<br>at Microsoft.Identity.Web.DefaultAuthorizationHeaderProvider.CreateAuthorizationHeaderForUserAsync(IEnumerable1 scopes, AuthorizationHeaderProviderOptions downstreamApiOptions, ClaimsPrincipal claimsPrincipal, CancellationToken cancellationToken)
at Microsoft.Identity.Web.TokenAcquisitionAuthenticationProvider.AuthenticateRequestAsync(HttpRequestMessage request)
at Microsoft.Graph.AuthenticationHandler.SendAsync(HttpRequestMessage httpRequestMessage, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
at Microsoft.Graph.HttpProvider.SendRequestAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
控制器代码(出错版本)
Task ReportTask = new Task(async () => { var reportPathOneDrive = "path.docx"; var reportPathBox = "path.docx"; var FileInfo = await oneDrive.DriveItemInfoAsync(SharedDriveID, reportPathOneDrive, "path"); var memoryStream = await oneDrive.DriveItemDownloadAsync(SharedDriveID, reportPathOneDrive, "path"); MemoryStream memoryStreamBox = new MemoryStream(); memoryStream.CopyTo(memoryStreamBox); memoryStream.Position = 0; memoryStreamBox.Position = 0; //add to email var attachment = new ARMS.Models.FileAttachment { FileName = FileInfo.Name.ToString(), ContentType = FileInfo.File.MimeType.ToString(), ContentBytes = memoryStream.ToArray() }; attachments.Add(attachment); var uploadBox = await boxDrive.UploadFileAsync(BoxARMSFolderID, memoryStreamBox, reportPathBox); }); ReportTask.Start(); List<Task> documentTasks = new List<Task> { ReportTask }; await Task.WhenAll(documentTasks);
出错的DriveItemDownloadAsync方法
public async Task<MemoryStream> DriveItemDownloadAsync(string DriveID, string PathOrDriveItemID, string SearchType) { var tries = 0; var maxRetries = 1; Stream response = null; var MemoryStream = new MemoryStream(); while (tries <= maxRetries) { tries++; try { if (SearchType == "path") { response = await _graphServiceClient.Me.Drives[DriveID].Root .ItemWithPath(PathOrDriveItemID) .Content .Request() .GetAsync(); } else if (SearchType == "id") { response = await _graphServiceClient.Me.Drives[DriveID] .Items[PathOrDriveItemID] .Content .Request() .GetAsync(); } response.CopyTo(MemoryStream); MemoryStream.Position = 0; tries = maxRetries + 1; } catch (ServiceException svcex) when (svcex.Message.Contains("Continuous access evaluation resulted in claims challenge")) { try { Console.WriteLine($"{svcex}"); string claimChallenge = WwwAuthenticateParameters.GetClaimChallengeFromResponseHeaders(svcex.ResponseHeaders); _consentHandler.ChallengeUser(initialScopes, claimChallenge); } catch (Exception ex2) { _consentHandler.HandleException(ex2); } } } return MemoryStream; }
问题根源
你使用new Task(async () => { ... })的方式是错误的:
- 异步Lambda会被包装成
Task<Task>,但你只拿到了外层的Task。当外层任务完成时,内层异步操作可能还在执行,此时控制器的请求上下文(包括IServiceProvider)已经被释放。 _graphServiceClient依赖请求上下文的身份认证服务,后续异步操作访问已释放的对象就会抛出错误。- 手动调用
ReportTask.Start()也是不必要的,异步代码无需手动启动任务。
修复方法
1. 提取独立异步方法
把异步逻辑封装成独立的异步方法,避免直接用异步Lambda创建Task:
async Task ProcessReportAsync() { var reportPathOneDrive = "path.docx"; var reportPathBox = "path.docx"; var FileInfo = await oneDrive.DriveItemInfoAsync(SharedDriveID, reportPathOneDrive, "path"); var memoryStream = await oneDrive.DriveItemDownloadAsync(SharedDriveID, reportPathOneDrive, "path"); using var memoryStreamBox = new MemoryStream(); memoryStream.CopyTo(memoryStreamBox); memoryStream.Position = 0; memoryStreamBox.Position = 0; //add to email var attachment = new ARMS.Models.FileAttachment { FileName = FileInfo.Name.ToString(), ContentType = FileInfo.File.MimeType.ToString(), ContentBytes = memoryStream.ToArray() }; attachments.Add(attachment); await boxDrive.UploadFileAsync(BoxARMSFolderID, memoryStreamBox, reportPathBox); }
2. 正确执行并行任务
在控制器中直接获取任务并等待,无需手动启动:
var reportTask = ProcessReportAsync(); List<Task> documentTasks = new List<Task> { reportTask }; await Task.WhenAll(documentTasks);
额外优化
- 用
using声明memoryStreamBox,确保流被正确释放 - 如果需要并行多个任务,直接添加多个异步方法返回的
Task到列表即可,无需Task.Run(除非任务不依赖请求上下文)
内容的提问来源于stack exchange,提问作者Qiuzman

