C#FileSystemWatcher,如何知道文件完全复制到watch文件夹中
我正在开发一个.net应用程序,我在其中使用FileSystemWatcher类并将其Created事件附加到文件夹上。 我必须对此事件采取行动(即将文件复制到其他位置)。 当我将大尺寸放入附加的监视文件夹时,即使文件复制过程仍未完成,事件立即引发。 我不想通过file.open方法检查这个。
有没有办法得到通知,我的文件复制过程已经完成,然后我的事件开始了。
在完全创建新文件时,FileSystemWatcher(和底层的ReadDirectoryChangesW API)无法获得通知,这确实令人失望。
迄今为止我遇到的最好和最安全的方法(并且不依赖于计时器)是这样的:
收到Created事件后,启动一个线程,该线程在循环中检查文件是否仍然被锁定(使用适当的重试间隔和最大重试次数)。 检查文件是否被锁定的唯一方法是尝试使用独占访问权限打开它:如果成功(不抛出IOException),则文件完成复制,并且您的线程可以引发适当的事件(例如FileCopyCompleted)。
我遇到了完全相同的问题,并以这种方式解决了这个问题:
- 设置
FileSystemWatcher
以在创建文件和修改文件时进行通知。 -
收到通知时:
一个。 如果没有为此文件名设置计时器(见下文),请将计时器设置为以适当的间隔到期(我通常使用1秒)。
湾 如果为此文件名设置了计时器,请取消计时器并将新计时器设置为在相同的时间间隔内到期。
当计时器到期时,您知道相关文件已创建或修改,并且在时间间隔内未受影响。 这意味着可能已完成复制/修改,您现在可以处理它。
您可以侦听已修改的事件,并启动计时器。 如果再次引发修改后的事件,请重置计时器。 当计时器达到某个值而没有引发修改事件时,您可以尝试执行复制。
我订阅了Changed
– 和Renamed
-event,并尝试在每个Changed
-event上重命名该文件,捕获IOExceptions。 如果重命名成功,则副本已完成, Rename
-event仅触发一次。
FileSystemWatcher有三个问题,第一个问题是它可以发送重复的创建事件,所以你可以用以下方法检查:
this.watcher.Created += (s, e) => { if (!this.seen.ContainsKey(e.FullPath) || (DateTime.Now - this.seen[e.FullPath]) > this.seenInterval) { this.seen[e.FullPath] = DateTime.Now; ThreadPool.QueueUserWorkItem( this.WaitForCreatingProcessToCloseFileThenDoStuff, e.FullPath); } };
其中this.seen
是Dictionary
, this.seenInterval
是TimeSpan
。
接下来,您必须等待文件创建者完成编写(问题中提出的问题)。 第三,你必须要小心,因为有时文件创建事件会在文件打开之前被抛出而不会给你一个FileNotFoundException
但它也可以被删除,然后你可以获取它也会产生FileNotFoundException
。
private void WaitForCreatingProcessToCloseFileThenDoStuff(object threadContext) { // Make sure the just-found file is done being // written by repeatedly attempting to open it // for exclusive access. var path = (string)threadContext; DateTime started = DateTime.Now; DateTime lastLengthChange = DateTime.Now; long lastLength = 0; var noGrowthLimit = new TimeSpan(0, 5, 0); var notFoundLimit = new TimeSpan(0, 0, 1); for (int tries = 0;; ++tries) { try { using (var fileStream = new FileStream( path, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) { // Do Stuff } break; } catch (FileNotFoundException) { // Sometimes the file appears before it is there. if (DateTime.Now - started > notFoundLimit) { // Should be there by now break; } } catch (IOException ex) { // mask in severity, customer, and code var hr = (int)(ex.HResult & 0xA000FFFF); if (hr != 0x80000020 && hr != 0x80000021) { // not a share violation or a lock violation throw; } } try { var fi = new FileInfo(path); if (fi.Length > lastLength) { lastLength = fi.Length; lastLengthChange = DateTime.Now; } } catch (Exception ex) { } // still locked if (DateTime.Now - lastLengthChange > noGrowthLimit) { // 5 minutes, still locked, no growth. break; } Thread.Sleep(111); }
当然,您可以设置自己的超时。 此代码留有足够的时间挂5分钟。 如果请求,真实代码也会有一个标志来退出线程。
尝试设置filter
myWatcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite;
这个答案有点晚了,但如果可能的话,我会让源进程在大文件或文件之后复制一个小标记文件并在其上使用FileWatcher。