Blazor中InputFile多文件并行上传遇空引用异常求助
Blazor Server多文件上传并行处理异常修复方案
问题根源
你遇到的NullReferenceException核心原因是Task.Run脱离了Blazor Server的Circuit同步上下文:browserFile.OpenReadStream()返回的RemoteJSDataStream依赖当前Circuit与浏览器端交互数据,线程池线程无法正确访问上下文关联的JS资源,导致流内部引用为空。此外代码还存在共享状态线程不安全、UI频繁更新的问题。
修复步骤
移除Task.Run,保留异步并行在组件上下文内执行
Blazor Server组件代码运行在专属同步上下文内,Task.Run会将任务移至线程池,破坏流与JS端的绑定关系,直接使用原始异步任务并行即可。线程安全处理共享状态
- 用
Interlocked.Add原子操作更新已读取字节数_alreadyRead,避免多线程竞态条件。 - 用
ConcurrentBag<IBrowserFile>替代普通List存储已处理文件,确保多线程添加操作安全。
- 用
优化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
相关产品推荐
相关产品推荐

