如何在TPL DataFlow流水线中预加载所有文件后再处理内容?
可以在单个TPL DataFlow流水线内实现该需求
完全可以通过调整流水线结构,加入缓存收集逻辑并利用DataFlow的完成通知机制,实现“先缓存所有二进制文件,再统一处理”的需求。核心思路是在文件读取块之后,新增一个负责收集所有文件数据到ConcurrentDictionary的块,等该块处理完所有输入后,再触发后续的处理块从缓存中读取数据执行CPU密集型操作。
方案一:分阶段收集缓存 + 触发后续处理
这种方案保留原有块的职责划分,通过完成回调触发处理阶段,适合需要单独控制各阶段并发的场景:
// 1. 定义全局缓存(流水线内共享) var asmCache = new ConcurrentDictionary<string, byte[]>(); var allFilePaths = new List<string>(); // 提前收集所有待处理的文件路径 // 解析项目文件块:输出二进制文件路径 var parseProjectFiles = new TransformBlock<string, string>(projectPath => { var filePath = ExtractBinaryPathFromMsBuildProject(projectPath); lock (allFilePaths) allFilePaths.Add(filePath); return filePath; }, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }); // 读取文件块:IO密集型,高并发 var readAsmFiles = new TransformBlock<string, (string Path, byte[] Data)>(filePath => { var fileData = File.ReadAllBytes(filePath); return (filePath, fileData); }, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 10 }); // 缓存收集块:将读取的文件数据存入字典 var cacheFiles = new ActionBlock<(string Path, byte[] Data)>(item => { asmCache.TryAdd(item.Path, item.Data); }, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 10 }); // 处理文件块:CPU密集型,按核心数设置并发 var processAsmFiles = new ActionBlock<string>(filePath => { if (asmCache.TryGetValue(filePath, out var data)) { // 执行你的CPU密集型处理逻辑 ProcessAssemblyContent(data); } }, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }); // 链接流水线各块,传递完成信号 parseProjectFiles.LinkTo(readAsmFiles, new DataflowLinkOptions { PropagateCompletion = true }); readAsmFiles.LinkTo(cacheFiles, new DataflowLinkOptions { PropagateCompletion = true }); // 关键:等缓存收集完成后,批量将文件路径传入处理块 cacheFiles.Completion.ContinueWith(_ => { foreach (var path in allFilePaths) { processAsmFiles.Post(path); } processAsmFiles.Complete(); }); // 启动流水线 parseProjectFiles.Post(@"C:\YourProject.csproj"); parseProjectFiles.Complete(); // 等待整个流水线执行完毕 await processAsmFiles.Completion;
方案二:用收集块直接输出完整缓存字典
这种方案将“收集所有文件数据到字典”封装为一个单独的Transform块,直接将完整字典传递给处理块,结构更紧凑:
// 解析项目文件块 var parseProjectFiles = new TransformBlock<string, string>(projectPath => { return ExtractBinaryPathFromMsBuildProject(projectPath); }, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }); // 读取文件块 var readAsmFiles = new TransformBlock<string, (string Path, byte[] Data)>(filePath => { return (filePath, File.ReadAllBytes(filePath)); }, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 10 }); // 收集所有文件数据到字典的块 var collectAndCache = new TransformBlock<(string Path, byte[] Data), ConcurrentDictionary<string, byte[]>>(async _ => { var buffer = new BufferBlock<(string Path, byte[] Data)>(); readAsmFiles.LinkTo(buffer, new DataflowLinkOptions { PropagateCompletion = true }); // 等待所有文件读取完成 await readAsmFiles.Completion; // 填充缓存字典 var asmCache = new ConcurrentDictionary<string, byte[]>(); while (buffer.TryReceive(out var item)) { asmCache.TryAdd(item.Path, item.Data); } return asmCache; }); // 处理文件块:接收完整字典并遍历处理 var processAsmFiles = new ActionBlock<ConcurrentDictionary<string, byte[]>>(cache => { foreach (var kvp in cache) { ProcessAssemblyContent(kvp.Value); } }, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }); // 链接流水线 parseProjectFiles.LinkTo(readAsmFiles, new DataflowLinkOptions { PropagateCompletion = true }); readAsmFiles.LinkTo(collectAndCache, new DataflowLinkOptions { PropagateCompletion = true }); collectAndCache.LinkTo(processAsmFiles, new DataflowLinkOptions { PropagateCompletion = true }); // 启动并等待完成 parseProjectFiles.Post(@"C:\YourProject.csproj"); parseProjectFiles.Complete(); await processAsmFiles.Completion;
关键注意点
- 并发控制:保持
readAsmFiles的高并发(IO密集型),processAsmFiles的并发数设为CPU核心数(CPU密集型),避免资源浪费。 - 完成信号传递:通过
DataflowLinkOptions.PropagateCompletion = true确保上游块完成时,下游块能收到完成通知。 - 线程安全:
ConcurrentDictionary本身是线程安全的,缓存收集块的并发写入无需额外锁。
内容的提问来源于stack exchange,提问作者mark
相关产品推荐
相关产品推荐

