FileSystemWatcher:忽略对文件的第一次更改
FileSystemWatcher: Ignore first change to the file
我在 C#
中使用 FileSystemWatcher
class 来跟踪用户在修改文件后保存文件时的更改。
FileSystemWatcher watcher = new FileSystemWatcher()
{
Path = DIR_NAME,
NotifyFilter = NotifyFilters.LastWrite |
NotifyFilters.CreationTime |
NotifyFilters.LastAccess,
Filter = "*.pdf",
IncludeSubdirectories = true,
EnableRaisingEvents = true
};
watcher.Changed += OnChanged;
但是,我要跟踪的文件是以编程方式创建的,如下所示:
FileStream stream = FileUtil.CreateNewFile(filePath); // Creates a file
file.WriteFileToStream(stream); // Writes into the file
理想情况下,我的代码应该 运行 如下:
- 程序将创建一个文件并向其中写入一些内容。
- 用户打开文件,修改并保存。
- 此时,它应该触发
OnChanged
,即我希望我的OnChanged
处理程序代码仅在真实用户修改并保存时执行。
但是,只要以编程方式写入文件,即在以下行中,它就会被触发:
file.WriteFileToStream(stream);
从技术上讲,这是正确的行为,因为它正在跟踪文件中的更改。但是,我的业务案例不允许在最初创建和写入文件时执行 OnChanged
处理程序代码。
我的问题是,在第一次以编程方式创建和写入文件时,是否有跳过 OnChanged
调用的解决方法?
注:
- 应用程序架构要求
FileSystemWatcher
在我的应用程序启动时进行初始化。所以我无法在创建文件后注册它。
- 这是一个多用户应用程序,其中多个用户将同时写入文件,因此我无法在创建文件之前禁用观察器并在创建文件后启用它:
watcher.EnableRaisingEvents = false;
CreateFile();
watcher.EnableRaisingEvents = true;
这是一个棘手的情况,除了检查文件是否被锁定(假设它有读锁或写锁)之外,您无能为力:
public bool IsFileLocked(string fileName)
{
try
{
using (var stream = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.None))
{
return false;
}
}
catch (IOException)
{
//the file is unavailable
return true;
}
}
前提是当你获得更新时,你检查文件是否被锁定,如果没有那么你可以宽松地假设它已被关闭。
不过需要考虑的一些事项:
- 当检查文件是否被锁定、清除所有内容,然后发现其他进程已锁定文件时,可能存在竞争条件。
- 我用过
FileAccess.Read
,所以这不会在只读文件上失败。
- 由于各种原因,事件可能会从
FileSystemWatcher
中删除,并且在某些情况下不能总是依赖它;阅读文档以了解可以删除事件的原因以及如何修复它们。在这些情况下,您可以通过长时间的民意调查和一些逻辑来挑选掉队的人(取决于您的情况)。
我在 OnChanged
处理程序中添加了以下代码,它似乎按预期工作。
private void OnChanged(object source, FileSystemEventArgs file)
{
if (file.ChangeType == WatcherChangeTypes.Created)
return;
FileInfo fileInfo = new FileInfo(file.FullPath);
if (fileInfo.CreationTime == fileInfo.LastWriteTime)
return;
// Handle the Changed event
...
}
但是,我不确定我是否遗漏了一些会导致它崩溃的东西。有什么想法吗?
方法一:
- 创建文件并将其保存在未被监视的目录中。
- 将文件移动到正在观看的目录中。
方法二:
创建文件后,使用 OnCreated()
事件处理程序将文件名添加到 filesToIgnore
列表。
在 OnChanged
事件处理程序中,检查 filesToIgnore
列表是否包含文件名。如果是,则将其从列表中删除(以便下次处理)并从处理程序中删除 return。
private List<string> filesToIgnore = new List<string>();
private void OnCreated(object source, FileSystemEventArgs file)
{
filesToIgnore.Add(file.Name);
}
private void OnChanged(object source, FileSystemEventArgs file)
{
if(filesToIgnore.Contains(file.Name))
{
filesToIgnore.Remove(file.Name);
return;
}
// Code to execute when user saves the file
}
此方法假定 OnCreated()
将始终在 OnChanged().
之前触发
下面的扩展方法只允许处理来自用户操作的事件:
public static void OnChangedByUser(this FileSystemWatcher fsw,
FileSystemEventHandler handler)
{
const int TOLERANCE_MSEC = 100;
object locker = new object();
string fileName = null;
Stopwatch stopwatch = new Stopwatch();
fsw.Created += OnFileCreated;
fsw.Changed += OnFileChanged;
fsw.Disposed += OnDisposed;
void OnFileCreated(object sender, FileSystemEventArgs e)
{
lock (locker)
{
fileName = e.Name;
stopwatch.Restart();
}
}
void OnFileChanged(object sender, FileSystemEventArgs e)
{
lock (locker)
{
if (e.Name == fileName && stopwatch.ElapsedMilliseconds < TOLERANCE_MSEC)
{
return; // Ignore this event
}
}
handler.Invoke(sender, e);
}
void OnDisposed(object sender, EventArgs e)
{
fsw.Created -= OnFileCreated;
fsw.Changed -= OnFileChanged;
fsw.Disposed -= OnDisposed;
}
}
用法示例:
var fsw = new FileSystemWatcher(DIR_NAME);
fsw.OnChangedByUser(File_ChangedByUser);
fsw.EnableRaisingEvents = true;
private static void File_ChangedByUser(object sender, FileSystemEventArgs e)
{
// Handle the event
}
我在 C#
中使用 FileSystemWatcher
class 来跟踪用户在修改文件后保存文件时的更改。
FileSystemWatcher watcher = new FileSystemWatcher()
{
Path = DIR_NAME,
NotifyFilter = NotifyFilters.LastWrite |
NotifyFilters.CreationTime |
NotifyFilters.LastAccess,
Filter = "*.pdf",
IncludeSubdirectories = true,
EnableRaisingEvents = true
};
watcher.Changed += OnChanged;
但是,我要跟踪的文件是以编程方式创建的,如下所示:
FileStream stream = FileUtil.CreateNewFile(filePath); // Creates a file
file.WriteFileToStream(stream); // Writes into the file
理想情况下,我的代码应该 运行 如下:
- 程序将创建一个文件并向其中写入一些内容。
- 用户打开文件,修改并保存。
- 此时,它应该触发
OnChanged
,即我希望我的OnChanged
处理程序代码仅在真实用户修改并保存时执行。
但是,只要以编程方式写入文件,即在以下行中,它就会被触发:
file.WriteFileToStream(stream);
从技术上讲,这是正确的行为,因为它正在跟踪文件中的更改。但是,我的业务案例不允许在最初创建和写入文件时执行 OnChanged
处理程序代码。
我的问题是,在第一次以编程方式创建和写入文件时,是否有跳过 OnChanged
调用的解决方法?
注:
- 应用程序架构要求
FileSystemWatcher
在我的应用程序启动时进行初始化。所以我无法在创建文件后注册它。 - 这是一个多用户应用程序,其中多个用户将同时写入文件,因此我无法在创建文件之前禁用观察器并在创建文件后启用它:
watcher.EnableRaisingEvents = false;
CreateFile();
watcher.EnableRaisingEvents = true;
这是一个棘手的情况,除了检查文件是否被锁定(假设它有读锁或写锁)之外,您无能为力:
public bool IsFileLocked(string fileName)
{
try
{
using (var stream = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.None))
{
return false;
}
}
catch (IOException)
{
//the file is unavailable
return true;
}
}
前提是当你获得更新时,你检查文件是否被锁定,如果没有那么你可以宽松地假设它已被关闭。
不过需要考虑的一些事项:
- 当检查文件是否被锁定、清除所有内容,然后发现其他进程已锁定文件时,可能存在竞争条件。
- 我用过
FileAccess.Read
,所以这不会在只读文件上失败。 - 由于各种原因,事件可能会从
FileSystemWatcher
中删除,并且在某些情况下不能总是依赖它;阅读文档以了解可以删除事件的原因以及如何修复它们。在这些情况下,您可以通过长时间的民意调查和一些逻辑来挑选掉队的人(取决于您的情况)。
我在 OnChanged
处理程序中添加了以下代码,它似乎按预期工作。
private void OnChanged(object source, FileSystemEventArgs file)
{
if (file.ChangeType == WatcherChangeTypes.Created)
return;
FileInfo fileInfo = new FileInfo(file.FullPath);
if (fileInfo.CreationTime == fileInfo.LastWriteTime)
return;
// Handle the Changed event
...
}
但是,我不确定我是否遗漏了一些会导致它崩溃的东西。有什么想法吗?
方法一:
- 创建文件并将其保存在未被监视的目录中。
- 将文件移动到正在观看的目录中。
方法二:
创建文件后,使用 OnCreated()
事件处理程序将文件名添加到 filesToIgnore
列表。
在 OnChanged
事件处理程序中,检查 filesToIgnore
列表是否包含文件名。如果是,则将其从列表中删除(以便下次处理)并从处理程序中删除 return。
private List<string> filesToIgnore = new List<string>();
private void OnCreated(object source, FileSystemEventArgs file)
{
filesToIgnore.Add(file.Name);
}
private void OnChanged(object source, FileSystemEventArgs file)
{
if(filesToIgnore.Contains(file.Name))
{
filesToIgnore.Remove(file.Name);
return;
}
// Code to execute when user saves the file
}
此方法假定 OnCreated()
将始终在 OnChanged().
下面的扩展方法只允许处理来自用户操作的事件:
public static void OnChangedByUser(this FileSystemWatcher fsw,
FileSystemEventHandler handler)
{
const int TOLERANCE_MSEC = 100;
object locker = new object();
string fileName = null;
Stopwatch stopwatch = new Stopwatch();
fsw.Created += OnFileCreated;
fsw.Changed += OnFileChanged;
fsw.Disposed += OnDisposed;
void OnFileCreated(object sender, FileSystemEventArgs e)
{
lock (locker)
{
fileName = e.Name;
stopwatch.Restart();
}
}
void OnFileChanged(object sender, FileSystemEventArgs e)
{
lock (locker)
{
if (e.Name == fileName && stopwatch.ElapsedMilliseconds < TOLERANCE_MSEC)
{
return; // Ignore this event
}
}
handler.Invoke(sender, e);
}
void OnDisposed(object sender, EventArgs e)
{
fsw.Created -= OnFileCreated;
fsw.Changed -= OnFileChanged;
fsw.Disposed -= OnDisposed;
}
}
用法示例:
var fsw = new FileSystemWatcher(DIR_NAME);
fsw.OnChangedByUser(File_ChangedByUser);
fsw.EnableRaisingEvents = true;
private static void File_ChangedByUser(object sender, FileSystemEventArgs e)
{
// Handle the event
}