C# 6异步Catch语句问题:返回ID但Azure Blob未保存求助
让我们一步步拆解你的代码和可能的问题场景:
1. 核心逻辑:返回ID的时机与异常流程的误解
先看你的代码结构:
private async Task<string> Save(Stream stream) { var id = Guid.NewGuid(); var blob = ...; try { await blob.UploadFromStreamAsync(stream); } catch { if (await blob.ExistsAsync()) { await blob.DeleteAsync(); } throw; } return id; }
这里有个容易被忽略的关键点:只有当UploadFromStreamAsync执行成功且没有抛出异常时,才会走到return id。如果上传抛出异常,catch块会执行throw,此时方法会直接终止,不会执行return id。那你能拿到ID,说明上传过程没有抛出异常,但Blob却不存在——这指向两种可能性:
a) 上传成功但Blob因最终一致性暂时不可见
Azure Blob存储默认是最终一致性:当上传操作成功返回后,Blob可能需要一小段时间才能在所有区域的存储节点中同步完成。如果你在拿到ID后立即去查询Blob,可能会遇到暂时找不到的情况,但过几分钟再看就会出现。
b) 上传操作“成功”但实际写入了空内容
如果传入的stream的Position已经在流的末尾(比如调用者之前已经读取过这个流),那么UploadFromStreamAsync会上传0字节的内容,此时SDK不会抛出异常,而是创建一个空Blob。如果你认为“空Blob不算成功保存”,这就会符合你描述的现象。
2. Catch块的潜在疏漏
你的空catch块和后续的ExistsAsync/DeleteAsync存在几个风险:
- 空catch捕获所有异常:包括非存储相关的异常(比如线程异常、参数错误等),这可能导致不必要的Blob删除操作,也不利于排查问题。
ExistsAsync本身可能抛出异常:比如网络波动时,检查Blob是否存在的请求失败,这会覆盖原上传异常,导致你无法得知真正的失败原因。- 最终一致性导致
ExistsAsync误判:如果上传失败时Blob处于中间状态,ExistsAsync可能返回false,导致你无法清理残留的不完整Blob。
3. 调用方式的问题(最常见的坑)
如果调用Save方法时没有使用await,比如:
// 错误调用:没有await,直接获取Result或忽略Task var blobId = Save(stream).Result;
这种情况下,代码会同步阻塞等待Task完成,但如果遇到上下文死锁(比如在UI线程或ASP.NET同步上下文),可能会导致上传操作卡住,最终Blob没有完成上传,但Result还是拿到了ID(因为Task在后台还没抛出异常)。或者更糟的是,你直接丢弃了Task,导致上传在后台执行,此时你立即查询Blob肯定找不到。
修复方案与优化建议
方案1:调整流的处理,确保上传完整内容
在上传前重置流的位置(如果流支持Seek),避免上传空内容:
try { if (stream.CanSeek) { stream.Position = 0; // 将流指针移到开头 } await blob.UploadFromStreamAsync(stream); } catch { // ... 原有逻辑 }
方案2:改进Catch块的健壮性
捕获具体的存储异常,避免覆盖原错误,同时给删除操作加独立的异常处理:
catch (StorageException ex) { // 只处理Azure存储相关的异常 try { if (await blob.ExistsAsync()) { await blob.DeleteAsync(); } } catch (Exception deleteEx) { // 记录删除失败的日志,不要覆盖原上传异常 // 例如:_logger.LogError(deleteEx, "Failed to delete partial blob {BlobId}", id); } throw; // 重新抛出原上传异常,让调用者处理 }
方案3:确保正确调用异步方法
调用Save时必须使用await,避免同步阻塞或丢弃Task:
// 正确调用 var blobId = await Save(myStream); // 之后查询Blob时,可以考虑等待一小段时间,或者使用强一致性检查(如果需要) var blob = container.GetBlobClient(blobId); // 使用强一致性检查Blob是否存在(仅限Blob存储V2) var exists = await blob.ExistsAsync(new BlobRequestConditions { IfModifiedSince = DateTimeOffset.UtcNow });
方案4:验证上传结果
可以在上传成功后,立即检查Blob的属性(比如大小),确保内容确实被写入:
await blob.UploadFromStreamAsync(stream); // 验证Blob大小是否符合预期 var properties = await blob.GetPropertiesAsync(); if (properties.ContentLength == 0 && stream.Length > 0) { // 上传失败但SDK没抛异常,手动抛出 throw new InvalidOperationException("Blob uploaded but content length is zero."); }
关于Azure SDK是否存在Bug?
从你的描述来看,SDK出现Bug的概率很低。更可能是上述逻辑或调用方式的问题。如果经过上述优化后仍然遇到问题,可以尝试:
- 更新Azure.Storage.Blobs包到最新版本,避免已知的旧版本Bug
- 开启SDK的日志记录,查看上传请求的详细响应,定位具体失败原因
内容的提问来源于stack exchange,提问作者Benjamin E.

