C#中处理大字节数组及多目标并行传输大文档的内存高效实现方案咨询
C#中处理大字节数组及多目标并行传输大文档的内存高效实现方案咨询
嗨,针对你在ASP.NET Core Web API里处理10-50MB大文档、并行分发到Salesforce、邮箱等多目标的内存高效需求,我来分享几个实际项目里跑通的方案,都是围绕避免全量内存拷贝、复用资源来设计的:
方案1:临时文件流+独立读实例(最通用、易维护)
这个方案核心是把Aspose处理后的输出流先写到磁盘临时文件,然后每个并行任务独立打开该文件的读流——因为文件流支持多进程/多线程共享读,每个任务操作自己的流位置,完全互不干扰,而且全程只有一份磁盘文件,内存里只会有每个流的小缓冲区(默认4KB左右),内存占用极低。
代码示例:
// 先把Aspose处理后的输出流写入临时文件(内存友好,不会把整个文档加载到内存) var tempFilePath = Path.GetTempFileName(); using var tempFileStream = new FileStream(tempFilePath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read); await processedStream.CopyToAsync(tempFileStream); await tempFileStream.FlushAsync(); tempFileStream.Position = 0; // 重置位置,不过每个任务会自己打开新的流实例 // 准备并行分发任务 var distributionTasks = new List<Task>(); // 任务1:发送到Salesforce distributionTasks.Add(Task.Run(async () => { using var salesforceUploadStream = new FileStream(tempFilePath, FileMode.Open, FileAccess.Read, FileShare.Read); // 这里替换成你实际的Salesforce上传逻辑 await SendDocumentToSalesforceAsync(salesforceUploadStream); })); // 任务2:发送到邮箱 distributionTasks.Add(Task.Run(async () => { using var emailAttachmentStream = new FileStream(tempFilePath, FileMode.Open, FileAccess.Read, FileShare.Read); // 这里替换成你实际的邮件发送逻辑 await SendDocumentAsEmailAttachmentAsync(emailAttachmentStream); })); // 等待所有分发任务完成 try { await Task.WhenAll(distributionTasks); } finally { // 无论成功失败,都清理临时文件 if (File.Exists(tempFilePath)) { File.Delete(tempFilePath); } }
方案2:内存映射文件(大文件性能最优)
如果担心磁盘IO的性能开销,可以用内存映射文件——它把磁盘文件映射到进程的虚拟内存空间,不用把整个文件加载到物理内存,多个线程可以各自创建独立的视图流访问文件内容,内存占用同样可控,而且读写性能比普通文件流更高(减少了用户态和内核态的内存拷贝)。
代码示例:
var tempFilePath = Path.GetTempFileName(); using var tempFileStream = new FileStream(tempFilePath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read); await processedStream.CopyToAsync(tempFileStream); await tempFileStream.FlushAsync(); // 创建内存映射文件 using var memoryMappedFile = MemoryMappedFile.CreateFromFile( tempFileStream, null, tempFileStream.Length, MemoryMappedFileAccess.Read, HandleInheritability.None, false); var distributionTasks = new List<Task>(); // 任务1:Salesforce上传 distributionTasks.Add(Task.Run(async () => { using var viewStream = memoryMappedFile.CreateViewStream(0, 0, MemoryMappedFileAccess.Read); await SendDocumentToSalesforceAsync(viewStream); })); // 任务2:邮件发送 distributionTasks.Add(Task.Run(async () => { using var viewStream = memoryMappedFile.CreateViewStream(0, 0, MemoryMappedFileAccess.Read); await SendDocumentAsEmailAttachmentAsync(viewStream); })); try { await Task.WhenAll(distributionTasks); } finally { if (File.Exists(tempFilePath)) { File.Delete(tempFilePath); } }
方案3:分块多播流(纯内存无磁盘依赖)
如果完全不想用临时文件(比如有安全合规要求),可以自己实现“分块多播”逻辑:把源流分块读取,每读取一块就同时并行写入所有目标流,全程只有一个小缓冲区(比如80KB)在内存里,完全不会加载整个文档。
代码示例:
// 定义一个通用的多播流方法,把源流分块转发给多个目标流 async Task MulticastStreamAsync(Stream sourceStream, params Stream[] destinationStreams) { if (sourceStream.CanSeek) { sourceStream.Position = 0; // 重置源流位置,确保从开头读取 } // 用80KB缓冲区,可根据你的服务器内存调整 var buffer = new byte[81920]; int bytesRead; while ((bytesRead = await sourceStream.ReadAsync(buffer, 0, buffer.Length)) > 0) { // 并行把当前块写入所有目标流 var writeTasks = destinationStreams.Select(dest => dest.WriteAsync(buffer, 0, bytesRead)).ToList(); await Task.WhenAll(writeTasks); } // 所有块写完后,刷新所有目标流 var flushTasks = destinationStreams.Select(dest => dest.FlushAsync()).ToList(); await Task.WhenAll(flushTasks); } // 使用示例 // 注意:这里的目标流需要是支持异步写入的,且每个流的生命周期由调用方管理 var salesforceUploadStream = await GetSalesforceUploadStreamAsync(); // 替换为你获取Salesforce上传流的逻辑 var emailAttachmentStream = await GetEmailAttachmentStreamAsync(); // 替换为你获取邮件附件流的逻辑 try { await MulticastStreamAsync(processedStream, salesforceUploadStream, emailAttachmentStream); } finally { // 记得关闭所有目标流 await salesforceUploadStream.DisposeAsync(); await emailAttachmentStream.DisposeAsync(); }
关键注意事项
- 确保源流可重读:如果Aspose返回的
processedStream是不可重读的(比如某些网络流、单向流),必须先把它转成可重读的流(比如临时文件流),否则无法并行读取。 - 控制并发数:不要无限制开并行任务,可通过
SemaphoreSlim限制并发数量,避免耗尽系统IO或网络资源。 - 异常处理:所有涉及流和文件的操作都要加
try/finally,确保资源被正确释放、临时文件被清理。
备注:内容来源于stack exchange,提问作者VJAI
相关产品推荐
相关产品推荐

