使用Azure Function上传至Amazon S3时进度无法达到100%
我之前做类似的跨云Blob传输时,也碰到过AWS SDK进度事件不触发100%的情况,结合你的场景,主要有几个可能的原因和对应的解决办法:
1. AWS SDK进度事件的触发逻辑限制
AWS .NET SDK的UploadProgressEvent在分块上传场景下,只会在每个分块传输完成时触发。当最后一个分块传输完成后,SDK还会执行合并分块的操作,但这一步的字节传输不会被计入UploadProgressArgs,也不会触发额外的进度事件。这就导致即使总传输字节数已经等于文件大小,最后一次触发的进度百分比可能还是不到100%(比如99%)。
解决办法:
在上传任务完成后(比如UploadAsync方法执行完毕),手动补充一条100%的进度记录。因为此时上传已经完全完成,不管进度事件有没有触发,都可以直接标记完成状态。
2. EF同步操作阻塞了事件线程
你在uploadRequest_UploadPartProgressEvent里直接用EF做同步数据库写入,这个操作是耗时的,会阻塞SDK用来触发进度事件的线程。如果线程被长时间占用,SDK可能会跳过后续的进度通知(包括最后100%的触发)。
解决办法:
- 把EF操作改成异步模式,用
await _context.SaveChangesAsync()替代同步的SaveChanges(),避免阻塞线程。 - 尽量不要在进度事件处理程序里做耗时操作,可将日志任务放入后台队列(比如Azure Function的
IBackgroundTaskQueue),让SDK的上传流程不受影响。
3. 进度计算的精度问题
有时候因为分块大小的计算,PercentDone可能会因为取整逻辑显示为99%,但实际BytesTransferred已经等于TotalBytesToTransfer。这时候可以通过字节数来判断是否完成,而不是依赖百分比。
示例代码调整
// 异步进度事件处理程序 private async void uploadRequest_UploadPartProgressEvent(object sender, UploadProgressArgs e) { using var scope = _serviceProvider.CreateScope(); var context = scope.ServiceProvider.GetRequiredService<YourDbContext>(); var progressLog = new UploadProgressLog { UploadId = "你的上传标识", BytesTransferred = e.BytesTransferred, Percentage = e.BytesTransferred == e.TotalBytesToTransfer ? 100 : e.PercentDone, Timestamp = DateTime.UtcNow }; context.UploadProgressLogs.Add(progressLog); await context.SaveChangesAsync(); } // 上传完成后补全100%进度 public async Task UploadToS3Async(Stream blobStream, string s3Key) { var uploadRequest = new TransferUtilityUploadRequest { InputStream = blobStream, Key = s3Key, BucketName = "你的S3桶名", }; uploadRequest.UploadProgressEvent += uploadRequest_UploadPartProgressEvent; var transferUtility = new TransferUtility(_s3Client); await transferUtility.UploadAsync(uploadRequest); // 上传完成后检查并补全100%进度 using var scope = _serviceProvider.CreateScope(); var context = scope.ServiceProvider.GetRequiredService<YourDbContext>(); var latestLog = await context.UploadProgressLogs .Where(l => l.UploadId == "你的上传标识") .OrderByDescending(l => l.Timestamp) .FirstOrDefaultAsync(); if (latestLog == null || latestLog.Percentage != 100) { context.UploadProgressLogs.Add(new UploadProgressLog { UploadId = "你的上传标识", BytesTransferred = blobStream.Length, Percentage = 100, Timestamp = DateTime.UtcNow }); await context.SaveChangesAsync(); } }
另外要注意,在Azure Function中使用EF时,要通过依赖注入正确管理上下文生命周期,避免在事件处理程序中持有长生命周期的上下文实例,防止出现资源泄漏或者并发问题。
内容的提问来源于stack exchange,提问作者Dan Young

