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

Blazor中InputFile多文件并行上传遇空引用异常求助

Blazor Server多文件上传并行处理异常修复方案

问题根源

你遇到的NullReferenceException核心原因是Task.Run脱离了Blazor Server的Circuit同步上下文:browserFile.OpenReadStream()返回的RemoteJSDataStream依赖当前Circuit与浏览器端交互数据,线程池线程无法正确访问上下文关联的JS资源,导致流内部引用为空。此外代码还存在共享状态线程不安全、UI频繁更新的问题。

修复步骤

  1. 移除Task.Run,保留异步并行在组件上下文内执行
    Blazor Server组件代码运行在专属同步上下文内,Task.Run会将任务移至线程池,破坏流与JS端的绑定关系,直接使用原始异步任务并行即可。

  2. 线程安全处理共享状态

    • 用Interlocked.Add原子操作更新已读取字节数_alreadyRead,避免多线程竞态条件。
    • 用ConcurrentBag<IBrowserFile>替代普通List存储已处理文件,确保多线程添加操作安全。
  3. 优化UI更新频率
    避免每次读取字节块都调用StateHasChanged,设置阈值(比如每读取64KB)再更新UI,减少上下文切换开销。

修改后的代码

using System.Collections.Concurrent;
using System.Threading;

private async Task HandleInputFileChange(InputFileChangeEventArgs fileChangeEventArgs)
{
    _filesProcessed.Clear();
    _alreadyRead = 0;

    var browserFiles = fileChangeEventArgs.GetMultipleFiles();
    _max = browserFiles.Sum(bf => bf.Size);

    // 线程安全集合存储已处理文件
    var processedFiles = new ConcurrentBag<IBrowserFile>();

    await Task.WhenAll(browserFiles.Select(async browserFile =>
    {
        var trustedFileName = Path.GetRandomFileName();
        var filePath = Path.Combine(HostEnvironment.ContentRootPath, HostEnvironment.EnvironmentName, FolderName, trustedFileName);

        await using var fileStream = new FileStream(filePath, FileMode.Create);
        await using var readStream = browserFile.OpenReadStream(AllowedFileSize);

        int bytesRead;
        var readBuffer = new byte[1024 * 64]; // 增大缓冲区减少IO次数
        long bytesReadInCurrentFile = 0;

        while ((bytesRead = await readStream.ReadAsync(readBuffer)) != 0)
        {
            // 原子操作累加已读字节数,避免多线程冲突
            Interlocked.Add(ref _alreadyRead, bytesRead);
            await fileStream.WriteAsync(readBuffer, 0, bytesRead);

            // 每读取64KB更新一次UI,降低更新频率
            bytesReadInCurrentFile += bytesRead;
            if (bytesReadInCurrentFile >= 1024 * 64)
            {
                await InvokeAsync(StateHasChanged);
                bytesReadInCurrentFile = 0;
            }
        }

        processedFiles.Add(browserFile);
        // 文件处理完成后强制更新一次UI
        await InvokeAsync(StateHasChanged);
    }));

    // 将线程安全集合的内容同步到组件绑定列表
    _filesProcessed.AddRange(processedFiles);
    await InvokeAsync(StateHasChanged);
}

额外优化建议

如果需要控制并发上传的文件数量(避免资源占用过高),可使用SemaphoreSlim限制并发数:

var semaphore = new SemaphoreSlim(3); // 最多同时处理3个文件
await Task.WhenAll(browserFiles.Select(async browserFile =>
{
    await semaphore.WaitAsync();
    try
    {
        // 原文件处理逻辑
    }
    finally
    {
        semaphore.Release();
    }
}));

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 23:10:31