C#:File.ReadLines多线程并行执行时线程分配异常求助
咱们先把问题根源理清楚:你遇到的"线程轮流霸占资源、其余线程闲置"的情况,本质是因为File.ReadLines返回的是延迟加载的单线程枚举器,它本身不是线程安全的。当多个任务线程同时去抢这个枚举器的控制权时,就会出现某个线程一次性拿到大量行数据处理,其他线程只能等待,等这个线程处理完一批,下一个线程才会抢到资源,循环往复,自然没法让你那10个线程都充分参与工作。
下面给你几个能彻底解决问题的方案,按需选择:
方案1:预先拆分文件块,让任务独立处理
核心思路是把整个文件拆成若干个独立的块(比如对应你要跑的4个任务),每个任务单独读取自己负责的块范围,完全避免线程间的资源竞争。
var fileInfo = new FileInfo("你的文件路径.txt"); // 按任务数拆分块大小,这里假设跑4个任务 long blockSize = fileInfo.Length / 4; var taskList = new List<Task>(); for (int i = 0; i < 4; i++) { int taskIdx = i; taskList.Add(Task.Run(() => { long startPos = taskIdx * blockSize; // 最后一个块要包含剩余所有内容 long endPos = taskIdx == 3 ? fileInfo.Length : (taskIdx + 1) * blockSize - 1; using (var reader = new StreamReader(File.OpenRead("你的文件路径.txt"))) { // 定位到当前块的起始位置 reader.BaseStream.Seek(startPos, SeekOrigin.Begin); // 处理边界:如果起始位置不是行首,跳过当前不完整的行 if (startPos > 0) { reader.ReadLine(); } // 读取到块的结束位置 string line; while ((line = reader.ReadLine()) != null && reader.BaseStream.Position <= endPos) { // 替换成你的业务处理逻辑 ProcessSingleLine(line); } } })); } await Task.WhenAll(taskList);
这个方案的优势是每个任务完全独立,不会互相干扰,能充分调动所有可用线程(包括你说的10个),而且性能稳定。需要注意的是一定要处理行边界,避免把一行拆成两个块。
方案2:生产者-消费者模式,统一分发任务
如果不想预先拆分文件,可以用一个生产者线程专门读取所有行,把行数据放到线程安全的队列里,再启动10个消费者线程从队列里取任务处理,保证所有线程都忙起来。
// 线程安全的阻塞队列,自动处理线程同步 var lineQueue = new BlockingCollection<string>(new ConcurrentQueue<string>()); // 生产者线程:负责读取所有行到队列 var producerTask = Task.Run(() => { foreach (var line in File.ReadLines("你的文件路径.txt")) { lineQueue.Add(line); } // 标记队列已完成添加,消费者线程会知道什么时候停止 lineQueue.CompleteAdding(); }); // 消费者线程:启动10个线程处理队列中的行 var consumerTasks = Enumerable.Range(0, 10).Select(_ => Task.Run(() => { foreach (var line in lineQueue.GetConsumingEnumerable()) { // 替换成你的业务处理逻辑 ProcessSingleLine(line); } })).ToList(); // 等待生产者和所有消费者任务完成 await Task.WhenAll(producerTask, Task.WhenAll(consumerTasks));
这个模式不用关心文件拆分的细节,BlockingCollection会自动帮你处理线程间的同步,10个消费者线程会持续从队列取任务,不会出现闲置的情况。
方案3:用Partitioner配合Parallel.ForEach均衡分配任务
如果想简化代码,可以用System.Collections.Concurrent.Partitioner来帮你拆分File.ReadLines的枚举流,让Parallel.ForEach能更均衡地把任务分配到10个线程上。
var allLines = File.ReadLines("你的文件路径.txt"); // 创建无缓冲的分区器,避免某个线程一次性拿到大量行 var partitioner = Partitioner.Create(allLines, EnumerablePartitionerOptions.NoBuffering); // 限制最大并行数为10,让所有线程都参与 Parallel.ForEach(partitioner, new ParallelOptions { MaxDegreeOfParallelism = 10 }, line => { // 替换成你的业务处理逻辑 ProcessSingleLine(line); });
这里的EnumerablePartitionerOptions.NoBuffering是关键,它会阻止分区器预先抓取大量行数据,而是按需分配给空闲的线程,保证10个线程都能均匀分到工作,不会出现某几个线程霸占资源的情况。
内容的提问来源于stack exchange,提问作者user8549339

