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

使用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,嵌入在多个视图中,用于按类别区分上传文件
  • 上传按钮为保存按钮,同时触发删除文件、保存页面更改、上传文件等多个异步/同步操作,逻辑复杂度较高

解决方案

客户端层面优化

  1. 优先完成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);
    }
    
  2. 拆分多任务异步执行
    保存按钮触发的删除、保存、上传操作需全部改为异步并行/顺序执行,避免同步操作阻塞主线程:
    // 示例:并行执行多个异步操作
    async function handleSave() {
        try {
            $('#loading-gif').show();
            // 并行执行删除、保存页面、上传操作
            await Promise.all([
                deleteOldFiles(),
                savePageChanges(),
                uploadFileToServer()
            ]);
            $('#loading-gif').hide();
        } catch (err) {
            console.error('保存失败:', err);
            $('#loading-gif').hide();
        }
    }
    

服务端+架构层面优化

  1. 客户端直接分块上传至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的有效期、权限范围控制。

  2. 服务端异步进度推送(可选)
    若必须通过服务端中转上传,可使用SignalR实时推送上传进度给客户端,避免客户端长时间等待AJAX响应:

    • 服务端在分块上传时,通过SignalR向客户端发送进度更新
    • 客户端接收进度消息后更新UI,保持页面响应

内容的提问来源于stack exchange,提问作者Andrew C

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 00:40:30