异步批量上传Azure Blobs出现MD5不匹配错误,求排查原因
异步上传Azure Blob出现MD5不匹配的问题分析及修复
问题1:SemaphoreSlim使用错误,未正确控制并发数
你的异步代码中,await semaphore.WaitAsync();被放在foreach循环之外,仅获取了一次信号量许可,后续创建的所有任务会同时执行,完全没达到限制50并发的目的。当batchSize远大于50时,会瞬间发起大量并发请求,进而引发:
- 网络资源竞争,部分请求的内容传输出现损坏
- Azure存储服务因并发过高,处理请求时出现MD5计算偏差
- 连接池耗尽,请求复用连接时出现数据混淆
正确用法:将semaphore.WaitAsync();移到每个任务内部,确保每个任务执行前先获取许可,完成后释放:
var semaphore = new SemaphoreSlim(50); var tasks = new List<Task>(); foreach (var callObj in source.Skip(curBatch * batchSize).Take(batchSize)) { // 显式捕获当前循环变量,避免闭包共享问题 var currentCallObj = callObj; tasks.Add(Task.Run(async () => { await semaphore.WaitAsync(); try { var name = ...; // 基于currentCallObj生成Blob名称 var blob = blobContainerForCalls.GetBlockBlobReference(name); var data = ...; // 基于currentCallObj生成待上传数据 await blob.UploadTextAsync(JsonConvert.SerializeObject(data)); } finally { semaphore.Release(); } })); } await Task.WhenAll(tasks);
问题2:闭包变量捕获的潜在风险
如果项目使用C#5之前的版本,foreach循环的callObj变量是在循环外部声明的,所有任务的闭包会捕获同一个callObj实例,最终所有任务都会使用循环最后一次的callObj数据。这会导致:
- 多个任务上传相同内容到不同Blob路径
- 序列化的
data与目标Blob不匹配,服务器计算的MD5自然和请求中的不一致
即使是C#5及以后版本(foreach变量每次循环重新声明),显式捕获当前变量也能避免潜在陷阱。
问题3:共享blob变量引发的资源竞争
从代码结构看,blob是方法级别的共享变量,多个异步任务会同时修改它,导致不同任务的Blob引用被覆盖。这会造成:
- 不同任务的上传内容被错误指向同一Blob路径
- 内容传输混乱,服务器接收的内容与请求MD5不匹配
修复:将blob变量声明在任务内部,确保每个任务拥有独立的Blob引用,消除竞争。
补充说明
同步代码正常的原因是它逐个处理请求,没有并发带来的资源竞争和变量共享问题,也不会因并发过高导致传输异常。
内容的提问来源于stack exchange,提问作者Matt Spinks
相关产品推荐
相关产品推荐

