使用FileSystemWatcher读取文件遇占用错误及线程优化咨询
问题解决方案
一、搞定“文件正在使用中”错误
1. 用正确的读取姿势
大概率是读取时没放开文件共享权限,导致和robocopy或者写入文件的程序冲突。直接用带共享模式的FileStream,读完自动释放资源:
string filePath = "你的文件路径"; using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) using (var sr = new StreamReader(fs)) { string content = sr.ReadToEnd(); // 拆分内容、插库的逻辑放这 }
using语句会自动关流释放资源,不会攥着文件不放。
2. 先复制再读,绕开robocopy冲突
robocopy每10分钟移文件,刚好撞上读取就会报错。先把文件复制到临时目录,读临时文件就不怕原文件被移动了:
string tempPath = Path.Combine(Path.GetTempPath(), Path.GetFileName(filePath)); // 复制时也加共享权限,防止原文件被占用 using (var sourceStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) using (var targetStream = new FileStream(tempPath, FileMode.Create, FileAccess.Write)) { sourceStream.CopyTo(targetStream); } // 读临时文件 using (var sr = new StreamReader(tempPath)) { string content = sr.ReadToEnd(); // 处理逻辑 } File.Delete(tempPath); // 读完删掉临时文件
3. 加重试机制,别一次就放弃
有时候文件只是暂时被占用,等几秒再试就行,最多重试3次:
int retryCount = 3; int waitMs = 1000; string content = null; while (retryCount > 0) { try { using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) using (var sr = new StreamReader(fs)) { content = sr.ReadToEnd(); break; } } catch (IOException ex) when ((ex.HResult & 0x0000FFFF) == 32) // 32是文件占用的错误码 { retryCount--; Thread.Sleep(waitMs); } } if (content == null) { // 记录日志,标记这个文件读取失败 }
二、优化效率和线程控制
1. 批量处理+批量插库
单个文件处理完再搞下一个太浪费时间,先把所有待处理文件的内容读出来,攒一批再插数据库,减少数据库连接的开销。还可以控制并行线程数,别把服务器搞崩:
string[] files = Directory.GetFiles("你的监控文件夹"); var dataList = new ConcurrentBag<YourDataModel>(); // 线程安全的集合存数据 // 控制最多4个线程并行处理,根据服务器配置调整 Parallel.ForEach(files, new ParallelOptions { MaxDegreeOfParallelism = 4 }, filePath => { // 用上面的安全方法读取文件内容 string content = ReadFileSafely(filePath); if (content == null) return; // 拆分内容成你的数据模型 var data = SplitContentToModel(content); dataList.Add(data); }); // 批量插库,用SqlBulkCopy比逐条Insert快N倍 using (var conn = new SqlConnection("你的数据库连接字符串")) { conn.Open(); using (var bulkCopy = new SqlBulkCopy(conn)) { bulkCopy.DestinationTableName = "目标表名"; // 映射列名,比如你的模型属性对应数据库列 bulkCopy.ColumnMappings.Add("Data1", "DB_Column1"); bulkCopy.ColumnMappings.Add("Data2", "DB_Column2"); // 把集合转成DataTable(或者直接用IEnumerable,.NET Core 3.0+支持) var dataTable = ConvertToDataTable(dataList); bulkCopy.WriteToServer(dataTable); } }
2. 用文件监控代替定时轮询
如果之前是定时扫文件夹,换成FileSystemWatcher实时监控文件创建事件,不用白耗资源轮询:
var watcher = new FileSystemWatcher("你的监控文件夹"); watcher.Created += (sender, e) => { // 注意:文件刚创建时可能还在写入,等半秒再处理,或者用重试逻辑 Thread.Sleep(500); ProcessFile(e.FullPath); // 这里调用你的文件处理方法 }; watcher.EnableRaisingEvents = true;
3. 复用数据库连接
别每次处理一个文件就新建连接,默认的连接池会自动管理连接,用using语句就行,不用手动关,连接池会把连接回收复用。
三、额外提醒
- 处理完的文件要么重命名,要么移到“已处理”文件夹,别重复处理。
- 加日志记录,比如哪个文件读失败了、插库报错了,方便排查问题。
- 测试时手动用robocopy移文件,看看还会不会出占用错误。
内容的提问来源于stack exchange,提问作者DottyMcNet
相关产品推荐
相关产品推荐

