我有一个应用程序,我正在寻找一个文本文件,如果对文件做了任何更改,我使用OnChanged事件处理程序来处理事件。我正在使用NotifyFilters。LastWriteTime,但是事件仍然被触发两次。这是代码。
public void Initialize()
{
FileSystemWatcher _fileWatcher = new FileSystemWatcher();
_fileWatcher.Path = "C:\\Folder";
_fileWatcher.NotifyFilter = NotifyFilters.LastWrite;
_fileWatcher.Filter = "Version.txt";
_fileWatcher.Changed += new FileSystemEventHandler(OnChanged);
_fileWatcher.EnableRaisingEvents = true;
}
private void OnChanged(object source, FileSystemEventArgs e)
{
.......
}
在我的情况下,OnChanged被调用两次,当我改变文本文件version.txt并保存它。
恐怕这是FileSystemWatcher类的一个众所周知的错误/特性。这是来自类的文档:
You may notice in certain situations that a single creation event generates multiple Created events that are handled by your component. For example, if you use a FileSystemWatcher component to monitor the creation of new files in a directory, and then test it by using Notepad to create a file, you may see two Created events generated even though only a single file was created. This is because Notepad performs multiple file system actions during the writing process. Notepad writes to the disk in batches that create the content of the file and then the file attributes. Other applications may perform in the same manner. Because FileSystemWatcher monitors the operating system activities, all events that these applications fire will be picked up.
现在这段文本是关于Created事件的,但同样的事情也适用于其他文件事件。在一些应用程序中,您可能能够通过使用NotifyFilter属性来解决这个问题,但我的经验是,有时您还必须进行一些手动重复过滤(hacks)。
前段时间我书签了一个页面,里面有一些FileSystemWatcher技巧。你可能会想去看看。
我必须结合以上文章中的几个想法,并添加文件锁定检查,让它为我工作:
FileSystemWatcher fileSystemWatcher;
private void DirectoryWatcher_Start()
{
FileSystemWatcher fileSystemWatcher = new FileSystemWatcher
{
Path = @"c:\mypath",
NotifyFilter = NotifyFilters.LastWrite,
Filter = "*.*",
EnableRaisingEvents = true
};
fileSystemWatcher.Changed += new FileSystemEventHandler(DirectoryWatcher_OnChanged);
}
private static void WaitUntilFileIsUnlocked(String fullPath, Action<String> callback, FileAccess fileAccess = FileAccess.Read, Int32 timeoutMS = 10000)
{
Int32 waitMS = 250;
Int32 currentMS = 0;
FileInfo file = new FileInfo(fullPath);
FileStream stream = null;
do
{
try
{
stream = file.Open(FileMode.Open, fileAccess, FileShare.None);
stream.Close();
callback(fullPath);
return;
}
catch (IOException)
{
}
finally
{
if (stream != null)
stream.Dispose();
}
Thread.Sleep(waitMS);
currentMS += waitMS;
} while (currentMS < timeoutMS);
}
private static Dictionary<String, DateTime> DirectoryWatcher_fileLastWriteTimeCache = new Dictionary<String, DateTime>();
private void DirectoryWatcher_OnChanged(Object source, FileSystemEventArgs ev)
{
try
{
lock (DirectoryWatcher_fileLastWriteTimeCache)
{
DateTime lastWriteTime = File.GetLastWriteTime(ev.FullPath);
if (DirectoryWatcher_fileLastWriteTimeCache.ContainsKey(ev.FullPath))
{
if (DirectoryWatcher_fileLastWriteTimeCache[ev.FullPath].AddMilliseconds(500) >= lastWriteTime)
return; // file was already handled
}
DirectoryWatcher_fileLastWriteTimeCache[ev.FullPath] = lastWriteTime;
}
Task.Run(() => WaitUntilFileIsUnlocked(ev.FullPath, fullPath =>
{
// do the job with fullPath...
}));
}
catch (Exception e)
{
// handle exception
}
}
我简单地添加了一个dupe检查如下:
private void OnChanged(object source, FileSystemEventArgs e)
{
string sTabName = Path.GetFileNameWithoutExtension(e.Name);
string sLastLine = ReadLastLine(e.FullPath);
if(sLastLine != _dupeCheck)
{
TabPage tp = tcLogs.TabPages[sTabName];
TextBox tbLog = (TextBox)tp.Controls[0] as TextBox;
tbLog.Invoke(new Action(() => tbLog.AppendText(sLastLine + Environment.NewLine)));
tbLog.Invoke(new Action(() => tbLog.SelectionStart = tbLog.Text.Length));
tbLog.Invoke(new Action(() => tbLog.ScrollToCaret()));
_dupeCheck = sLastLine;
}
}
public static String ReadLastLine(string path)
{
return ReadLastLine(path, Encoding.Default, "\n");
}
public static String ReadLastLine(string path, Encoding encoding, string newline)
{
int charsize = encoding.GetByteCount("\n");
byte[] buffer = encoding.GetBytes(newline);
using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
long endpos = stream.Length / charsize;
for (long pos = charsize; pos < endpos; pos += charsize)
{
stream.Seek(-pos, SeekOrigin.End);
stream.Read(buffer, 0, buffer.Length);
if (encoding.GetString(buffer) == newline)
{
buffer = new byte[stream.Length - stream.Position];
stream.Read(buffer, 0, buffer.Length);
return encoding.GetString(buffer);
}
}
}
return null;
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
private const int WM_VSCROLL = 0x115;
private const int SB_BOTTOM = 7;
/// <summary>
/// Scrolls the vertical scroll bar of a multi-line text box to the bottom.
/// </summary>
/// <param name="tb">The text box to scroll</param>
public static void ScrollToBottom(TextBox tb)
{
SendMessage(tb.Handle, WM_VSCROLL, (IntPtr)SB_BOTTOM, IntPtr.Zero);
}
我可以通过添加一个函数来检查缓冲区数组中的重复项来做到这一点。
然后使用计时器在数组X时间内未被修改后执行该操作:
-每次有东西写入缓冲区时重置计时器
—对tick执行动作
这还捕获了另一种复制类型。如果修改文件夹内的文件,该文件夹也会引发Change事件。
Function is_duplicate(str1 As String) As Boolean
If lb_actions_list.Items.Count = 0 Then
Return False
Else
Dim compStr As String = lb_actions_list.Items(lb_actions_list.Items.Count - 1).ToString
compStr = compStr.Substring(compStr.IndexOf("-") + 1).Trim
If compStr <> str1 AndAlso compStr.parentDir <> str1 & "\" Then
Return False
Else
Return True
End If
End If
End Function
Public Module extentions
<Extension()>
Public Function parentDir(ByVal aString As String) As String
Return aString.Substring(0, CInt(InStrRev(aString, "\", aString.Length - 1)))
End Function
End Module