使用Azure Blob Storage上传文件时浏览器无响应问题求助
大文件上传至Azure Blob Storage时Chrome提示页面无响应的解决思路
问题描述
项目支持用户上传文件至Azure Blob Storage,大文件上传耗时较长属于预期情况,但当文件体积足够大时,Chrome会判定上传中的标签页无响应,弹出"终止页面/等待"提示。此时上传仍在后台进行,但页面完全冻结,UI元素(加载GIF、进度条)无法正常显示,控制台也无输出,终止页面会直接取消上传。
已尝试的无效方案
- 添加加载GIF、UI进度条、控制台输出等进度跟踪功能,均无效果:上传开始后页面立即冻结,GIF仅在上传完成页面重载时闪现,控制台全程无输出
- 调用上传方法前提前显示加载GIF,未生效
相关代码
客户端上传调用代码
function CallUploadFile(fileData, successFunction, errorFunction) { $('#loading-gif').show() // Calling method where upload to Blob Storage is after showing loading gif $.ajax({ url: '/File/UploadFile', type: 'post', data: data, async: true, success: function (fileData) { successFunction(fileData); }, error: function (fileData) { errorFunction(fileData); }, ... }); }
服务端UploadFile方法
public async Task<string> UploadFile() { BlobServiceClient service = new BlobServiceClient(connectionString); BlobContainerClient container = blobServiceClient.GetBlobContainerClient(blobContainer); BlockBlobClient block = container.GetBlockBlobClient(fileName); using (Stream stream = inputStream) { byte[] buffer = new byte[1024 * 1024 * 50]; int bytesRead; int blockNumber = 0; while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0) { using (MemoryStream chunkStream = new MemoryStream(buffer, 0, bytesRead)) { string blockId = Convert.ToBase64String(BitConverter.GetBytes(blockNumber)); await block.StageBlockAsync(blockId, chunkStream); blockNumber++; } } var blockList = Enumerable.Range(0, blockNumber) .Select(x => Convert.ToBase64String(BitConverter.GetBytes(x))) .ToList(); await block.CommitBlockListAsync(blockList); } return "Success"; }
补充信息
- 上传页面为Partial View,嵌入在多个视图中,用于按类别区分上传文件
- 上传按钮为保存按钮,同时触发删除文件、保存页面更改、上传文件等多个异步/同步操作,逻辑复杂度较高
解决方案
客户端层面优化
- 优先完成UI渲染再执行上传
浏览器UI线程与JS线程共用同一主线程,调用$('#loading-gif').show()后需给浏览器留出渲染时间,再执行上传逻辑。可通过setTimeout延迟AJAX调用:function CallUploadFile(fileData, successFunction, errorFunction) { $('#loading-gif').show(); // 延迟10ms执行上传,让浏览器先完成UI渲染 setTimeout(() => { $.ajax({ url: '/File/UploadFile', type: 'post', data: data, async: true, // 添加XHR进度监听,实时更新UI xhr: function() { const xhr = new XMLHttpRequest(); xhr.upload.addEventListener('progress', (e) => { if (e.lengthComputable) { const percent = (e.loaded / e.total) * 100; console.log(`上传进度:${percent.toFixed(2)}%`); $('#progress-bar').css('width', `${percent}%`); } }); return xhr; }, success: (res) => successFunction(res), error: (err) => errorFunction(err), ... }); }, 10); } - 拆分多任务异步执行
保存按钮触发的删除、保存、上传操作需全部改为异步并行/顺序执行,避免同步操作阻塞主线程:// 示例:并行执行多个异步操作 async function handleSave() { try { $('#loading-gif').show(); // 并行执行删除、保存页面、上传操作 await Promise.all([ deleteOldFiles(), savePageChanges(), uploadFileToServer() ]); $('#loading-gif').hide(); } catch (err) { console.error('保存失败:', err); $('#loading-gif').hide(); } }
服务端+架构层面优化
客户端直接分块上传至Azure Blob Storage
绕过服务端中转,使用Azure Storage JS SDK在客户端直接完成分块上传,既减轻服务端负载,又能实时跟踪每个分块的上传进度,彻底避免页面无响应:async function uploadDirectToBlob(file, sasToken, accountName, containerName) { const blobServiceClient = new BlobServiceClient(`https://${accountName}.blob.core.windows.net/?${sasToken}`); const containerClient = blobServiceClient.getContainerClient(containerName); const blockBlobClient = containerClient.getBlockBlobClient(file.name); const blockSize = 5 * 1024 * 1024; // 5MB分块(Azure单块最大100MB) const blocks = []; let offset = 0; while (offset < file.size) { const chunk = file.slice(offset, offset + blockSize); const blockId = btoa(String(offset)); // 生成唯一块ID await blockBlobClient.stageBlock(blockId, chunk); blocks.push(blockId); offset += blockSize; // 实时更新进度 const progress = (offset / file.size) * 100; $('#progress-bar').css('width', `${progress}%`); } await blockBlobClient.commitBlockList(blocks); return "上传成功"; }注:服务端只需生成具有上传权限的SAS Token返回给客户端,无需处理文件流,安全性可通过SAS Token的有效期、权限范围控制。
服务端异步进度推送(可选)
若必须通过服务端中转上传,可使用SignalR实时推送上传进度给客户端,避免客户端长时间等待AJAX响应:- 服务端在分块上传时,通过SignalR向客户端发送进度更新
- 客户端接收进度消息后更新UI,保持页面响应
内容的提问来源于stack exchange,提问作者Andrew C
相关产品推荐
相关产品推荐

