如何在C#中检测文件是否已完全移动至目标文件夹?
针对文件夹监控PDF压缩工具的文件未完全写入问题优化方案
问题背景
开发了一款Windows文件夹监控工具,触发条件为新文件添加,核心功能是压缩目标文件夹内的PDF文件。此前运行正常,但遇到低速网络传输大体积PDF时,工具会在文件尚未完全移入完成时被触发,引发异常。
当前初步方案逻辑:检测到PDF文件后尝试读取全部字节,失败则等待2分钟重试一次,仍失败则记录错误并忽略该文件。但原代码存在明显问题(如Timer启动后立刻停止,异步回调无法正确影响主线程逻辑),需要优化。
原方案问题分析
- Timer使用错误:代码中启动Timer后立刻调用
Stop(),导致Elapsed事件根本不会触发,重试逻辑完全失效。 - 异步逻辑与单线程冲突:Elapsed回调在后台线程执行,对
pdfFreeToUse的修改无法被主线程及时感知,主线程会直接return,后续压缩代码无法执行。 - 读取整个文件效率低:对大体积PDF使用
File.ReadAllBytes会占用大量内存,且检测文件是否可用的方式过于笨重。
优化思路
- 轻量文件可用性检测:通过尝试以独占模式打开文件来判断是否已写入完成,无需读取整个文件。
- 同步重试机制:在单线程环境下使用同步重试+延迟,避免异步带来的线程安全问题。
- 可配置重试策略:允许设置重试次数和间隔,适配不同网络环境。
- 非阻塞监控(可选):若单线程阻塞影响监控,可将文件处理逻辑放入后台线程,保持监控线程持续运行。
优化后代码示例
bool pdfFreeToUse = false; int retryCount = int.Parse(Config.ConfigExtractor("PdfRetryCount")); // 配置重试次数 int retryIntervalMs = int.Parse(Config.ConfigExtractor("PdfRetryIntervalMillisecond")); // 配置重试间隔(毫秒) for (int i = 0; i <= retryCount; i++) { try { // 尝试以独占方式打开文件,验证是否可访问 using (FileStream fs = new FileStream(PdfPath, FileMode.Open, FileAccess.Read, FileShare.None)) { pdfFreeToUse = true; break; } } catch (IOException) { // 文件被占用,等待后重试 if (i < retryCount) { System.Threading.Thread.Sleep(retryIntervalMs); } else { // 重试次数耗尽,记录错误 string errorMessage = $"无法访问PDF文件 {PdfName},可能被其他进程占用或已损坏。"; EventProtocol.CreateEventLog(); EventProtocol.WriteErrorEventLog(errorMessage); WriteErrorAsTxt($"{PdfPath}\\{PdfName}_Error.txt", errorMessage); } } catch (Exception ex) { // 其他异常直接记录 string errorMessage = $"处理PDF文件 {PdfName} 时发生未知错误:{ex.Message}"; EventProtocol.CreateEventLog(); EventProtocol.WriteErrorEventLog(errorMessage); WriteErrorAsTxt($"{PdfPath}\\{PdfName}_Error.txt", errorMessage); break; } } if (!pdfFreeToUse) { return; // 无法处理,终止当前流程 } // 执行PDF压缩逻辑
额外建议
- 监控事件过滤:在文件夹监控时,可先过滤掉临时文件(如下载中的
.tmp文件),只处理已完成写入的.pdf文件。 - 文件大小校验:记录文件第一次被检测到的大小,间隔一段时间后再次检查大小,若大小一致则认为写入完成。
- 后台线程处理:如果工具是单线程监控,可将PDF压缩和重试逻辑放入
Task中执行,避免阻塞监控线程接收新的文件夹变更事件。
内容的提问来源于stack exchange,提问作者Ado
相关产品推荐
相关产品推荐

