这个活主要是最近为ai喂数据时用到了,按照之前的单线程处理方式,一个文件平均2秒,30000个文件就是60000秒。
等等! 如果你还在用传统的单线程方式处理PDF文件,那你就OUT了!
今天我来教你一招狠活儿:使用C#的Parallel编程,让PDF批量处理效率瞬间提升10倍!从此告别加班熬夜,让领导对你刮目相看!
数据说话:
C#的Parallel.ForEach
是我们的救星!它能够:
BashInstall-Package iTextSharp 或是 Install-Package iTextSharp-LGPL //这个是更好的选择
C#using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using iTextSharp.text.pdf.parser;
using iTextSharp.text.pdf;
namespace AppParallelPdf
{
public class ParallelPdfProcessor
{
/// <summary>
/// 并行读取PDF文件并提取文本
/// </summary>
/// <param name="pdfDirectoryPath">PDF文件目录</param>
/// <returns>包含PDF文件名和文本内容的线程安全字典</returns>
public ConcurrentDictionary<string, string> ProcessPdfFilesInParallel(string pdfDirectoryPath)
{
// 🔐 线程安全字典:多线程环境下的数据存储利器
var processedPdfTexts = new ConcurrentDictionary<string, string>();
// 📁 获取目录下所有PDF文件
string[] pdfFiles = Directory.GetFiles(pdfDirectoryPath, "*.pdf");
// 🚀 核心:使用Parallel.ForEach并行处理
Parallel.ForEach(pdfFiles, new ParallelOptions
{
// ⚡ 关键配置:限制最大并行度
// 防止创建过多线程导致系统资源耗尽
MaxDegreeOfParallelism = Environment.ProcessorCount
},
(pdfFilePath) =>
{
try
{
// 提取PDF文件名
string fileName = System.IO.Path.GetFileName(pdfFilePath);
// 🎯 核心操作:提取PDF文本内容
string pdfText = ExtractPdfText(pdfFilePath);
// 🔒 线程安全添加:ConcurrentDictionary的威力
processedPdfTexts[fileName] = pdfText;
Console.WriteLine($"✅ 成功处理文件: {fileName}");
}
catch (Exception ex)
{
// 🛡️ 异常处理:单个文件失败不影响整体流程
Console.WriteLine($"❌ 处理文件 {pdfFilePath} 时发生错误: {ex.Message}");
}
});
return processedPdfTexts;
}
/// <summary>
/// 提取PDF文本内容的核心方法
/// </summary>
/// <param name="pdfPath">PDF文件路径</param>
/// <returns>PDF文本内容</returns>
private string ExtractPdfText(string pdfPath)
{
// 🔍 使用iTextSharp读取PDF
using (PdfReader reader = new PdfReader(pdfPath))
{
var textBuilder = new System.Text.StringBuilder();
// 📖 逐页读取文本内容
for (int i = 1; i <= reader.NumberOfPages; i++)
{
string pageText = PdfTextExtractor.GetTextFromPage(reader, i);
textBuilder.Append(pageText);
}
return textBuilder.ToString();
}
}
/// <summary>
/// PDF文本分析示例:关键词统计
/// </summary>
/// <param name="processedPdfTexts">处理后的PDF文本字典</param>
public void AnalyzePdfTexts(ConcurrentDictionary<string, string> processedPdfTexts)
{
// 🔬 再次使用并行处理进行文本分析
Parallel.ForEach(processedPdfTexts, (pdfEntry) =>
{
string fileName = pdfEntry.Key;
string content = pdfEntry.Value;
// 📊 简单的词数统计示例
int wordCount = content.Split(new[] { ' ', '\n', '\r' },
StringSplitOptions.RemoveEmptyEntries).Length;
Console.WriteLine($"📈 文件 {fileName} 总词数: {wordCount}");
});
}
}
}
C#namespace AppParallelPdf
{
internal class Program
{
static void Main(string[] args)
{
Console.OutputEncoding = System.Text.Encoding.UTF8;
Console.WriteLine("🚀 开始处理PDF文件...");
var processor = new ParallelPdfProcessor();
// 📂 指定PDF文件目录
string pdfDirectory = @"D:\手册\ABB";
// ⏱️ 开始计时
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
// 🚀 并行处理PDF文件
var processedPdfs = processor.ProcessPdfFilesInParallel(pdfDirectory);
// 📊 分析处理结果
processor.AnalyzePdfTexts(processedPdfs);
stopwatch.Stop();
Console.WriteLine($"🎉 处理完成!总耗时: {stopwatch.ElapsedMilliseconds}ms");
Console.WriteLine($"📁 成功处理文件数: {processedPdfs.Count}");
}
}
}
C#MaxDegreeOfParallelism = Environment.ProcessorCount
为什么这样设置?
C#var processedPdfTexts = new ConcurrentDictionary<string, string>();
普通Dictionary的问题:
ConcurrentDictionary的优势:
C#try
{
// 处理单个PDF文件
}
catch (Exception ex)
{
// 单个文件失败不影响其他文件处理
}
实战意义:
C#// ❌ 错误做法:不限制并行度
Parallel.ForEach(files, (file) => { ... });
// ✅ 正确做法:限制并行度
Parallel.ForEach(files, new ParallelOptions
{
MaxDegreeOfParallelism = Environment.ProcessorCount
}, (file) => { ... });
C#// ❌ 一个文件出错,全部停止
Parallel.ForEach(files, (file) =>
{
ProcessFile(file); // 可能抛异常
});
// ✅ 异常隔离,继续处理其他文件
Parallel.ForEach(files, (file) =>
{
try
{
ProcessFile(file);
}
catch (Exception ex)
{
LogError(file, ex);
}
});
C#// ❌ Dictionary在多线程下会出错
var results = new Dictionary<string, string>();
// ✅ 使用线程安全的ConcurrentDictionary
var results = new ConcurrentDictionary<string, string>();
C#var partitioner = Partitioner.Create(files, true);
Parallel.ForEach(partitioner, (file) =>
{
// 处理逻辑
});
C#// 大文件处理时,及时释放内存
using (var fileStream = new FileStream(path, FileMode.Open))
{
// 处理文件
} // 自动释放资源
C#private int processedCount = 0;
Parallel.ForEach(files, (file) =>
{
ProcessFile(file);
int current = Interlocked.Increment(ref processedCount);
Console.WriteLine($"进度: {current}/{files.Length}");
});
使用Parallel.ForEach
可以将CPU密集型任务的处理效率提升3-10倍,特别适合文件批量处理场景。
选择合适的线程安全集合(如ConcurrentDictionary
)和正确的异常处理策略,是并行编程成功的基础。
合理设置并行度、及时释放资源、监控系统性能,在效率和稳定性之间找到最佳平衡点。
问题1:你在项目中遇到过哪些适合并行处理的场景?处理效果如何?
问题2:除了PDF处理,你还用并行编程解决过什么实际问题?
分享你的经验,让更多C#开发者受益! 👇
觉得这篇文章对你有帮助吗?
关注我,获取更多C#开发实战干货! 🚀
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!