17

I'm monitoring a folder using a FileSystemWatcher like this:

watcher = new FileSystemWatcher(folder);
watcher.NotifyFilter = NotifyFilters.Size;
watcher.Changed += changedCallback;

When I open a new file in notepad in that folder and save it, I get a notification. If I keep writing and then I save, I get a notification. If I close the file with saving it, I get a notification. Exactly what I wanted.

However, it turns out that if I create a file in that folder and I set its sharing mode to FileShare.Read, and then I write to it, I will not get any notifications until the file is closed. Another workaround is to open the file (e.g. in Notepad), which apparently causes its state to be updated, and then my monitoring application gets the notification. Yet another workaround is a refresh I can do in Windows Explorer, which again causes the file state to be updated.

Interestingly, if I look at Windows Explorer while I make the changes, I notice that:

  1. If the file is shared for reading & writing, its size will be updated immediately in Windows Explorer as soon as I save it.
  2. If the file is shared for reading only, its size will NOT be update immediately in Windows Explorer, unless I manually refresh the window.

So it seems that my monitoring application shares the same behavior as Windows Explorer. I was thinking about running a thread that will just scan the files in the folder, but I'm wondering if there's anything more elegant to be done in this case.

BTW, I'm using Win7 and I'm not sure that this problem happens on other Windows versions as well.

Thanks!

EDIT: Using ReadDirectoryChanges in C++ got me the same exact results. Implementing the thread I was talking about earlier didn't help as well. I'm wondering what is F5 in Windows Explorer actually doing, because it does cause the change to be reported.

tanascius
  • 53,078
  • 22
  • 114
  • 136
Eldad Mor
  • 5,405
  • 3
  • 34
  • 46

6 Answers6

13

The solution to the problem is not to open the files, but to actually READ from them. It's enough to read even one byte, and the Windows cache mechanism will write the contents of the file to the disk, thus allowing you to read them.

I ended up implementing a thread that went over all the files, opened them and read a byte from them. This caused them to change, and triggered the event in the FileSystemWatcher object.

The reason that Windows Explorer F5 works as well, is that Windows actually reads the contents of the file in order to show some extended contents (e.g., thumbnails). Once the file is being read, the cache first writes to the disk, triggering the event in the FSW.

Eldad Mor
  • 5,405
  • 3
  • 34
  • 46
4

Yes, Explorer uses the exact same API as FileSystemWatcher uses. There's just one, ReadDirectoryChangesW() as you found out.

What you found strongly suggest that Win7 is optimizing the disk write that would be needed to update the directory entry for the file. Delaying it until the last possible moment, when the file is closed. There's an interesting correlation between this observation and a critical bug that a user discovered in the RTM version of Win7. The update sometimes doesn't happen at all. The bug strikes randomly but infrequently, I've seen this myself just once on my machine. Knowingly anyway.

The details are in this thread (beware of very slow server). This still fails today with all Win7 updates applied.

Well, interesting tidbit but not really germane to your question. You do need to alter your code to accommodate the OS works.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Thanks for the reply, Hans. Still, given that I cannot alter the writing application, do you have any suggestions how I should proceed? I wonder how can I mimic Explorer's F5 (which does update the file size and causes my application to fire the event). I tried getting the files list and even opening them one by one, but it didn't help. – Eldad Mor Aug 22 '10 at 19:11
  • Hmm, interesting that Explorer can get your FSW event to fire. I can only think of FlushFileBuffers() but surely that's not it. Maybe you can see it back in a SysInternals' ProcMon stack trace. – Hans Passant Aug 22 '10 at 19:23
  • 1
    Turns out that Explorer reads the files, leading (this is my own theory) to the cache mechanism flushing the write to the disk, so that the reader could actually get the most updated contents. See my answer to the question for details. Also, thanks for your comments! – Eldad Mor Aug 29 '10 at 13:57
4

I ran into this same FileSystemWatcher issue while writing a Windows Service. The service was written in .NET and used a FileSystemWatcher to monitor log files generated by a third party product. During testing I performed actions within this third party product that I knew forced log entries to be written but my service's breakpoints never fired until I opened my target log file in notepad or refreshed my view in Windows Explorer.

My solution was to create a FileInfo instance (we'll call that fileInfoInstance) at the same time as I created my FileSystemWatcher. Any time I start or stop my FileSystemWatcher I also start or stop a System.Threading.Timer whose callback invokes fileInfoInstance.Refresh() every N milliseconds. It appears that fileInfoInstance.Refresh() flushes the buffers/write caching and allows FileSystemWatcher events to raise in the same manner that hitting F5 does within Explorer.

Interestingly (and sadly) enough fileInfoInstance.Directory.Refresh() didn't accomplish the same result, so if you're watching multiple files, even if they're all in the same directory and being watched by the same watcher, you'll need a FileInfo instance for each file you're watching and your timer callback should refresh them all with each "tick"...

Happy coding.

Brian

Brian
  • 2,772
  • 15
  • 12
  • Well eventually I solved the problem by opening all the files for reading. Holding a FileInfo instance per file in that case would not be very suitable, since files are being added and deleted in the directory, and there can be several hundreds of them. Reading them sequentially was better in my case, but I can see how your case differs. – Eldad Mor Nov 10 '10 at 22:31
2

Looks like the file data is cached and not actually written. When you write something to the file, the data is first placed to cache using certain file system IRP (driver request). Now when the data is actually written to the disk, another IRP is sent. It can be (this is a guess) that FileSystemWatcher catches only second IRP type.

The solution (if possible) is to call Flush on the file you are writing. If you are tracking changes made by other applications, things can become more complicated, of course.

Eugene Mayevski 'Callback
  • 45,135
  • 8
  • 71
  • 121
  • Thanks. Unfortunately, I'm not controlling the writing application. – Eldad Mor Aug 22 '10 at 10:50
  • I think knowing what F5 in Explorer is doing would not help a lot either, since F5 is an active action and you can't take such action just because you don't know when the file is changed. – Eugene Mayevski 'Callback Aug 22 '10 at 12:10
  • I was thinking about simulating the refresh in my own thread. My other option is to rewrite the FileSystemWatcher in a thread of my own that would check the file - but even that might not work if the data is not actually written to the disk. Refreshing Windows Explorer seems to flush the buffer to the disk, which is why I want to understand how it works. – Eldad Mor Aug 22 '10 at 12:23
  • Maybe this discussion will help: http://stackoverflow.com/questions/173560/flush-disk-write-cache – Eugene Mayevski 'Callback Aug 22 '10 at 12:39
  • Eugene, thanks for the assistance. Your insights have put me on the right track. You can see my answer for the details. – Eldad Mor Aug 29 '10 at 12:06
  • Interesting to know, thank you for pointing at your discoveries. – Eugene Mayevski 'Callback Aug 29 '10 at 12:57
1

You indeed need a thread that would open all files and read a byte from them, my program stopped working after I ran it on Windows 7 instead of XP, I used the following code

private void SingleByteReadThread(object notUsed)
    {
       while (true)
      {
         foreach (FileInfo fi in new DirectoryEnumerator(folderPath))
                  {
                      using (FileStream fs = new FileStream(fi.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                          fs.ReadByte();    
                  }

          Thread.Sleep(TimeSpan.FromSeconds(2));
      }
  }

DirectoryEnumerator is my own class

Valentin Kuzub
  • 11,703
  • 7
  • 56
  • 93
  • 1
    Just for the record, this can be dont with Directory.EnumerateFiles instead of some custom DirectoryEnumerator aswell and works perfectly! – CBenni Aug 17 '13 at 02:45
0

You have to call FileStream Flush() method to write file changes.

i486
  • 6,491
  • 4
  • 24
  • 41