You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# 6异步Catch语句问题:返回ID但Azure Blob未保存求助

分析你的问题:返回ID但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.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 08:00:56