将多个文件添加到目录时,FileSystemWatcher出现文件访问错误
当多个文件被放入监视的目录时,我遇到了FileSystemWatcher的问题。 我想parsing文件,只要它被放置在目录中。 通常情况下,第一个文件parsing正常,但将第二个文件添加到目录会导致访问问题。 偶尔,第一个文件甚至不parsing。 只有一个应用程序正在运行并看着这个目录。 最终,这个过程将在多台机器上运行,他们将会看到一个共享目录,但只有一台服务器可以parsing每个文件,因为数据被导入到数据库中,并且没有主键。
这是FileSystemWatcher代码:
public void Run() { FileSystemWatcher watcher = new FileSystemWatcher("C:\\temp"); watcher.NotifyFilter = NotifyFilters.FileName; watcher.Filter = "*.txt"; watcher.Created += new FileSystemEventHandler(OnChanged); watcher.EnableRaisingEvents = true; System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite); }
然后parsing该文件的方法:
private void OnChanged(object source, FileSystemEventArgs e) { string line = null; try { using (FileStream fs = new FileStream(e.FullPath, FileMode.Open, FileAccess.Read, FileShare.None)) { using (StreamReader sr = new StreamReader(fs)) { while (sr.EndOfStream == false) { line = sr.ReadLine(); //parse the line and insert into the database } } } } catch (IOException ioe) { Console.WriteLine("OnChanged: Caught Exception reading file [{0}]", ioe.ToString()); }
移动第二个文件时,它正在捕捉
System.IO.IOException:进程无法访问文件“C:\ Temp \ TestFile.txt”,因为它正在被另一个进程使用。
我希望看到这个错误,如果它运行在多台机器上,但它现在只在一台服务器上运行。 不应该有使用这个文件的另一个进程 – 我创build了它们,并在应用程序运行时将它们复制到目录中。
这是设置FileSystemWatcher的正确方法吗? 我怎样才能看到这个文件有什么锁? 为什么不parsing这两个文件 – 我必须closuresFileStream吗? 我想保留FileShare.None选项,因为我只希望一个服务器parsing文件 – 首先到达文件的服务器parsing它。
这种方法的一个典型问题是在事件被触发时文件仍然被复制。 显然,你会得到一个例外,因为在复制过程中文件被locking。 大文件特别可能出现exception。
作为一种解决方法,您可以先复制该文件,然后重命名并收听重命名事件。
或者另一个选项是有一个while循环检查文件是否可以用写入权限打开。 如果可以的话,你会知道复制已经完成。 C#代码可能如下所示(在生产系统中,您可能希望具有最大数量的重试或超时而不是while(true)
):
/// <summary> /// Waits until a file can be opened with write permission /// </summary> public static void WaitReady(string fileName) { while (true) { try { using (Stream stream = System.IO.File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)) { if (stream != null) { System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} ready.", fileName)); break; } } } catch (FileNotFoundException ex) { System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} not yet ready ({1})", fileName, ex.Message)); } catch (IOException ex) { System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} not yet ready ({1})", fileName, ex.Message)); } catch (UnauthorizedAccessException ex) { System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} not yet ready ({1})", fileName, ex.Message)); } Thread.Sleep(500); } }
另一种方法是在复制完成后在文件夹中放置一个小的触发文件。 您的FileSystemWatcher只会侦听触发器文件。
我上面留下了一个评论,但是我还没有足够的分数。
这个问题的评分最高的答案有一个如下所示的代码块:
using (Stream stream = System.IO.File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)) { if (stream != null) { System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} ready.", fileName)); break; } }
使用FileShare.ReadWrite
设置的问题是,它正在请求访问文件,基本上说“我要读取/写入此文件,但其他人也可以读取/写入”。 这种做法在我们的情况下失败。 正在接收远程传输的进程没有locking该文件,但正在积极地写入该文件。 我们的下游代码(SharpZipLib)与“正在使用的文件”exception失败,因为它试图用FileShare.Read
(“我希望文件读取,只让其他进程读取”)打开文件。 由于打开文件的进程已经写入,所以此请求失败。
但是,上面的响应中的代码太放松了。 通过使用FileShare.ReadWrite
,它成功地获得了文件的访问权限(因为它要求可以遵守共享限制),但下游调用继续失败。
调用File.Open
的共享设置应该是FileShare.Read
或FileShare.None
,而不是 FileShare.ReadWrite
。
当您在OnChanged方法中打开文件时,您指定了FileShare.None
,根据文档 ,将导致其他任何尝试在打开文件时打开文件失败。 由于你(和你的观察者)正在做的是阅读,请尝试使用FileShare.Read
。
一旦你收到通知,简单的解决办法就是处理文件系统监视器。 复制文件之前,使当前线程等待,直到它收到filesystemwatcher处置事件。 那么您可以继续复制已更改的文件而不会出现访问问题 我有相同的要求,我完全像我所说的。 有效。
示例代码:
public void TestWatcher() { using (var fileWatcher = new FileSystemWatcher()) { string path = @"C:\sv"; string file = "pos.csv"; fileWatcher.Path = path; fileWatcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite; fileWatcher.Filter = file; System.EventHandler onDisposed = (sender,args) => { eve.Set(); }; FileSystemEventHandler onFile = (sender, fileChange) => { fileWatcher.EnableRaisingEvents = false; Thread t = new Thread(new ParameterizedThreadStart(CopyFile)); t.Start(fileChange.FullPath); if (fileWatcher != null) { fileWatcher.Dispose(); } proceed = false; }; fileWatcher.Changed += onFile; fileWatcher.Created += onFile; fileWatcher.Disposed+= onDisposed; fileWatcher.EnableRaisingEvents = true; while (proceed) { if (!proceed) { break; } } } } public void CopyFile(object sourcePath) { eve.WaitOne(); var destinationFilePath = @"C:\sv\Co"; if (!string.IsNullOrEmpty(destinationFilePath)) { if (!Directory.Exists(destinationFilePath)) { Directory.CreateDirectory(destinationFilePath); } destinationFilePath = Path.Combine(destinationFilePath, "pos.csv"); } File.Copy((string)sourcePath, destinationFilePath); }
FileSystemWatcher会在启动文件复制时启动每个单个文件创build1ce的创build事件两次 ,并在完成文件复制时启动第二次。 你所要做的就是第二次忽略第一个事件和处理事件。
事件处理程序的一个简单例子:
private bool _fileCreated = false; private void FileSystemWatcher_FileCreated(object sender, FileSystemEventArgs e) { if (_fileCreated) { ReadFromFile();//just an example method call to access the new file } _fileCreated = !_fileCreated; }
我觉得你想要的是Log4net中的ConfigureAndWatchHandler的一个很好的例子。 他们使用一个计时器来触发文件处理程序事件。 我觉得这最终是在0xA3的post中的while循环的更清晰的实现。 对于那些不想使用dotPeek来检查文件的人,我会尝试给你一个基于OP代码的代码片段:
private System.Threading.Timer _timer; public void Run() { //setup filewatcher _timer = new System.Threading.Timer(new TimerCallback(OnFileChange), (object) null, -1, -1); } private void OnFileChange(object state) { try { //handle files } catch (Exception ex) { //log exception _timer.Change(500, -1); } }
我有类似的问题。 它只是因为FileSystemWatcher。 我刚刚用过
Thread.sleep代码();
而且现在工作得很好。 当文件进入目录时,它会调用onCreated两次。 所以一旦文件被复制,第二次复制完成。 为此,我使用Thread.Sleep(); 所以它会在我调用ReadFile()之前等待。
private static void OnCreated(object source, FileSystemEventArgs e) { try { Thread.Sleep(5000); var data = new FileData(); data.ReadFile(e.FullPath); } catch (Exception ex) { WriteLogforError(ex.Message, String.Empty, filepath); } }
我在DFS中遇到了同样的问题。 我的决议是通过在每个文件中添加两个空行来实现的。 然后我的代码等待文件中的两个空行。 那么我肯定会从文件中读取整个数据。
public static BitmapSource LoadImageNoLock(string path) { while (true) { try { var memStream = new MemoryStream(File.ReadAllBytes(path)); var img = new BitmapImage(); img.BeginInit(); img.StreamSource = memStream; img.EndInit(); return img; break; } catch (Exception ex) { Console.WriteLine(ex.Message); } } }