我有一个应用程序,我正在寻找一个文本文件,如果对文件做了任何更改,我使用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
被调用两次。
答案 0 :(得分:266)
我担心这是FileSystemWatcher
类的一个众所周知的错误/特征。这来自该课程的文档:
在某些情况下,您可能会注意到单个创建事件会生成由组件处理的多个Created事件。例如,如果使用FileSystemWatcher组件来监视目录中新文件的创建,然后使用记事本创建文件进行测试,即使只创建了一个文件,也可能会看到生成两个Created事件。这是因为Notepad在写入过程中执行多个文件系统操作。记事本批量写入磁盘,创建文件的内容,然后创建文件属性。其他应用程序可以以相同的方式执行。由于FileSystemWatcher监视操作系统活动,因此将拾取这些应用程序触发的所有事件。
现在这段文字是关于Created
事件的,但同样的事情也适用于其他文件事件。在某些应用程序中,您可以使用NotifyFilter
属性来解决这个问题,但我的经验表明,有时您还需要进行一些手动重复过滤(黑客攻击)。
前一段时间我预订了一个包含FileSystemWatcher tips的网页。你可能想看一下。
答案 1 :(得分:142)
我已在我的代理中使用以下策略“修复”了该问题:
// fsw_ is the FileSystemWatcher instance used by my application.
private void OnDirectoryChanged(...)
{
try
{
fsw_.EnableRaisingEvents = false;
/* do my stuff once asynchronously */
}
finally
{
fsw_.EnableRaisingEvents = true;
}
}
答案 2 :(得分:104)
通过检查相关文件的OnChanged
时间戳,可以检测并丢弃FileSystemWatcher
中的任何重复File.GetLastWriteTime
个事件。像这样:
DateTime lastRead = DateTime.MinValue;
void OnChanged(object source, FileSystemEventArgs a)
{
DateTime lastWriteTime = File.GetLastWriteTime(uri);
if (lastWriteTime != lastRead)
{
doStuff();
lastRead = lastWriteTime;
}
// else discard the (duplicated) OnChanged event
}
答案 3 :(得分:22)
这是我的解决方案,帮助我阻止事件被提升两次:
watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size;
这里我将NotifyFilter
属性设置为仅包含文件名和大小
watcher
是FileSystemWatcher的对象。希望这会有所帮助。
答案 4 :(得分:8)
我的方案是我有一台带有Linux服务器的虚拟机。我正在Windows主机上开发文件。当我在主机上的文件夹中更改某些内容时,我希望上传所有更改,并通过Ftp同步到虚拟服务器上。这是我写入文件时删除重复更改事件的方法(标记包含要修改的文件的文件夹):
private Hashtable fileWriteTime = new Hashtable();
private void fsw_sync_Changed(object source, FileSystemEventArgs e)
{
string path = e.FullPath.ToString();
string currentLastWriteTime = File.GetLastWriteTime( e.FullPath ).ToString();
// if there is no path info stored yet
// or stored path has different time of write then the one now is inspected
if ( !fileWriteTime.ContainsKey(path) ||
fileWriteTime[path].ToString() != currentLastWriteTime
)
{
//then we do the main thing
log( "A CHANGE has occured with " + path );
//lastly we update the last write time in the hashtable
fileWriteTime[path] = currentLastWriteTime;
}
}
主要是我创建一个哈希表来存储文件写入时间信息。然后,如果哈希表具有被修改的文件路径,并且它的时间值与当前通知的文件的更改相同,那么我知道它是事件的副本并忽略它。
答案 5 :(得分:7)
这是我的方法:
// Consider having a List<String> named _changedFiles
private void OnChanged(object source, FileSystemEventArgs e)
{
lock (_changedFiles)
{
if (_changedFiles.Contains(e.FullPath))
{
return;
}
_changedFiles.Add(e.FullPath);
}
// do your stuff
System.Timers.Timer timer = new Timer(1000) { AutoReset = false };
timer.Elapsed += (timerElapsedSender, timerElapsedArgs) =>
{
lock (_changedFiles)
{
_changedFiles.Remove(e.FullPath);
}
};
timer.Start();
}
这是我在一个项目中解决此问题的解决方案,我在该项目中将文件作为附件发送到邮件中。 即使定时器间隔较小,它也很容易避免两次发射事件,但在我的情况下,1000就好了,因为我很幸福,因为我发现缺少一些变化而不是使用&gt;充斥邮箱。每秒1条消息。 至少它可以正常工作,以防几个文件在同一时间更改。
我想到的另一个解决方案是用字典映射文件替换列表到它们各自的MD5,所以你不必选择任意间隔,因为你不必删除条目但是更新它价值,并取消你的东西,如果它没有改变。 随着文件被监控并占用越来越多的内存,它有一个字典在内存中增长的缺点,但我已经读过某个地方,监控的文件数量取决于FSW的内部缓冲区,所以可能不那么重要。 不知道MD5计算时间如何影响代码的性能,小心= \
答案 6 :(得分:6)
尝试使用此代码:
class WatchPlotDirectory
{
bool let = false;
FileSystemWatcher watcher;
string path = "C:/Users/jamie/OneDrive/Pictures/Screenshots";
public WatchPlotDirectory()
{
watcher = new FileSystemWatcher();
watcher.Path = path;
watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
| NotifyFilters.FileName | NotifyFilters.DirectoryName;
watcher.Filter = "*.*";
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.Renamed += new RenamedEventHandler(OnRenamed);
watcher.EnableRaisingEvents = true;
}
void OnChanged(object sender, FileSystemEventArgs e)
{
if (let==false) {
string mgs = string.Format("File {0} | {1}",
e.FullPath, e.ChangeType);
Console.WriteLine("onchange: " + mgs);
let = true;
}
else
{
let = false;
}
}
void OnRenamed(object sender, RenamedEventArgs e)
{
string log = string.Format("{0} | Renamed from {1}",
e.FullPath, e.OldName);
Console.WriteLine("onrenamed: " + log);
}
public void setPath(string path)
{
this.path = path;
}
}
答案 7 :(得分:4)
我知道这是一个老问题,但是遇到了同样的问题,并且上述解决方案都没有真正解决我遇到的问题。我创建了一个字典,用于将文件名映射到LastWriteTime。因此,如果文件不在字典中,则继续进行其他方式检查以查看最后修改时间的时间,以及是否与字典中的内容不同运行代码。
Dictionary<string, DateTime> dateTimeDictionary = new Dictionary<string, DateTime>();
private void OnChanged(object source, FileSystemEventArgs e)
{
if (!dateTimeDictionary.ContainsKey(e.FullPath) || (dateTimeDictionary.ContainsKey(e.FullPath) && System.IO.File.GetLastWriteTime(e.FullPath) != dateTimeDictionary[e.FullPath]))
{
dateTimeDictionary[e.FullPath] = System.IO.File.GetLastWriteTime(e.FullPath);
//your code here
}
}
答案 8 :(得分:3)
这是您可以尝试的新解决方案。对我来说效果很好。在已更改事件的事件处理程序中,以编程方式从设计器输出中删除处理程序,如果需要,则以编程方式添加处理程序。例如:
public void fileSystemWatcher1_Changed( object sender, System.IO.FileSystemEventArgs e )
{
fileSystemWatcher1.Changed -= new System.IO.FileSystemEventHandler( fileSystemWatcher1_Changed );
MessageBox.Show( "File has been uploaded to destination", "Success!" );
fileSystemWatcher1.Changed += new System.IO.FileSystemEventHandler( fileSystemWatcher1_Changed );
}
答案 9 :(得分:3)
一种可能的“黑客攻击”是使用Reactive Extensions来限制事件,例如:
var watcher = new FileSystemWatcher("./");
Observable.FromEventPattern<FileSystemEventArgs>(watcher, "Changed")
.Throttle(new TimeSpan(500000))
.Subscribe(HandleChangeEvent);
watcher.EnableRaisingEvents = true;
在这种情况下,我节制到50ms,在我的系统上已经足够了,但更高的值应该更安全。 (就像我说的,它仍然是'黑客')。
答案 10 :(得分:3)
我创建了一个带有扩展FileSystemWatcher
的类的Git仓库,只有在复制完成后才触发事件。它会丢弃所有已更改的事件,只有当文件可供读取时才会引发它。
下载FileSystemSafeWatcher并将其添加到您的项目中。
然后将其用作普通FileSystemWatcher
并监控事件的触发时间。
var fsw = new FileSystemSafeWatcher(file);
fsw.EnableRaisingEvents = true;
// Add event handlers here
fsw.Created += fsw_Created;
答案 11 :(得分:3)
我在这里有一个非常快速和简单的解决方法,它确实对我有用,并且无论事件偶尔会被触发一次或两次或更多次,请查看:
private int fireCount = 0;
private void inputFileWatcher_Changed(object sender, FileSystemEventArgs e)
{
fireCount++;
if (fireCount == 1)
{
MessageBox.Show("Fired only once!!");
dowork();
}
else
{
fireCount = 0;
}
}
}
答案 12 :(得分:2)
主要是为了将来的我:)。
我使用Rx编写了一个包装器:
public class WatcherWrapper : IDisposable
{
private readonly FileSystemWatcher _fileWatcher;
private readonly Subject<FileSystemEventArgs> _infoSubject;
private Subject<FileSystemEventArgs> _eventSubject;
public WatcherWrapper(string path, string nameFilter = "*.*", NotifyFilters? notifyFilters = null)
{
_fileWatcher = new FileSystemWatcher(path, nameFilter);
if (notifyFilters != null)
{
_fileWatcher.NotifyFilter = notifyFilters.Value;
}
_infoSubject = new Subject<FileSystemEventArgs>();
_eventSubject = new Subject<FileSystemEventArgs>();
Observable.FromEventPattern<FileSystemEventArgs>(_fileWatcher, "Changed").Select(e => e.EventArgs)
.Subscribe(_infoSubject.OnNext);
Observable.FromEventPattern<FileSystemEventArgs>(_fileWatcher, "Created").Select(e => e.EventArgs)
.Subscribe(_infoSubject.OnNext);
Observable.FromEventPattern<FileSystemEventArgs>(_fileWatcher, "Deleted").Select(e => e.EventArgs)
.Subscribe(_infoSubject.OnNext);
Observable.FromEventPattern<FileSystemEventArgs>(_fileWatcher, "Renamed").Select(e => e.EventArgs)
.Subscribe(_infoSubject.OnNext);
// this takes care of double events and still works with changing the name of the same file after a while
_infoSubject.Buffer(TimeSpan.FromMilliseconds(20))
.Select(x => x.GroupBy(z => z.FullPath).Select(z => z.LastOrDefault()).Subscribe(
infos =>
{
if (infos != null)
foreach (var info in infos)
{
{
_eventSubject.OnNext(info);
}
}
});
_fileWatcher.EnableRaisingEvents = true;
}
public IObservable<FileSystemEventArgs> FileEvents => _eventSubject;
public void Dispose()
{
_fileWatcher?.Dispose();
_eventSubject.Dispose();
_infoSubject.Dispose();
}
}
用法:
var watcher = new WatcherWrapper(_path, "*.info");
// all more complicated and scenario specific filtering of events can be done here
watcher.FileEvents.Where(x => x.ChangeType != WatcherChangeTypes.Deleted).Subscribe(x => //do stuff)
答案 13 :(得分:2)
这段代码对我有用。
private void OnChanged(object source, FileSystemEventArgs e)
{
string fullFilePath = e.FullPath.ToString();
string fullURL = buildTheUrlFromStudyXML(fullFilePath);
System.Diagnostics.Process.Start("iexplore", fullURL);
Timer timer = new Timer();
((FileSystemWatcher)source).Changed -= new FileSystemEventHandler(OnChanged);
timer.Interval = 1000;
timer.Elapsed += new ElapsedEventHandler(t_Elapsed);
timer.Start();
}
private void t_Elapsed(object sender, ElapsedEventArgs e)
{
((Timer)sender).Stop();
theWatcher.Changed += new FileSystemEventHandler(OnChanged);
}
答案 14 :(得分:2)
主要原因是 第一个事件的最后访问时间是当前时间(文件写入或更改时间)。 然后第二个事件是文件的原始最后访问时间。 我在代码下解决。
var lastRead = DateTime.MinValue;
Watcher = new FileSystemWatcher(...)
{
NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite,
Filter = "*.dll",
IncludeSubdirectories = false,
};
Watcher.Changed += (senderObject, ea) =>
{
var now = DateTime.Now;
var lastWriteTime = File.GetLastWriteTime(ea.FullPath);
if (now == lastWriteTime)
{
return;
}
if (lastWriteTime != lastRead)
{
// do something...
lastRead = lastWriteTime;
}
};
Watcher.EnableRaisingEvents = true;
答案 15 :(得分:2)
我花了大量时间使用FileSystemWatcher,这里的一些方法不起作用。我真的很喜欢禁用事件方法,但遗憾的是,如果删除了> 1个文件,它就无法工作,如果不是所有时间都会丢失第二个文件。 所以我使用以下方法:
private void EventCallback(object sender, FileSystemEventArgs e)
{
var fileName = e.FullPath;
if (!File.Exists(fileName))
{
// We've dealt with the file, this is just supressing further events.
return;
}
// File exists, so move it to a working directory.
File.Move(fileName, [working directory]);
// Kick-off whatever processing is required.
}
答案 16 :(得分:1)
事件如果没有被问到,很遗憾F#没有现成的解决方案样本。 解决这个问题的方法是我的方法,因为我能和F#是一种很棒的.NET语言。
使用FSharp.Control.Reactive
包过滤掉重复的事件,这只是反应性扩展的F#包装器。所有这些都可以针对完整框架或netstandard2.0
:
let createWatcher path filter () =
new FileSystemWatcher(
Path = path,
Filter = filter,
EnableRaisingEvents = true,
SynchronizingObject = null // not needed for console applications
)
let createSources (fsWatcher: FileSystemWatcher) =
// use here needed events only.
// convert `Error` and `Renamed` events to be merded
[| fsWatcher.Changed :> IObservable<_>
fsWatcher.Deleted :> IObservable<_>
fsWatcher.Created :> IObservable<_>
//fsWatcher.Renamed |> Observable.map renamedToNeeded
//fsWatcher.Error |> Observable.map errorToNeeded
|] |> Observable.mergeArray
let handle (e: FileSystemEventArgs) =
printfn "handle %A event '%s' '%s' " e.ChangeType e.Name e.FullPath
let watch path filter throttleTime =
// disposes watcher if observer subscription is disposed
Observable.using (createWatcher path filter) createSources
// filter out multiple equal events
|> Observable.distinctUntilChanged
// filter out multiple Changed
|> Observable.throttle throttleTime
|> Observable.subscribe handle
[<EntryPoint>]
let main _args =
let path = @"C:\Temp\WatchDir"
let filter = "*.zip"
let throttleTime = TimeSpan.FromSeconds 10.
use _subscription = watch path filter throttleTime
System.Console.ReadKey() |> ignore
0 // return an integer exit code
答案 17 :(得分:1)
尝试一下,一切正常
private static readonly FileSystemWatcher Watcher = new FileSystemWatcher();
static void Main(string[] args)
{
Console.WriteLine("Watching....");
Watcher.Path = @"D:\Temp\Watcher";
Watcher.Changed += OnChanged;
Watcher.EnableRaisingEvents = true;
Console.ReadKey();
}
static void OnChanged(object sender, FileSystemEventArgs e)
{
try
{
Watcher.Changed -= OnChanged;
Watcher.EnableRaisingEvents = false;
Console.WriteLine($"File Changed. Name: {e.Name}");
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
finally
{
Watcher.Changed += OnChanged;
Watcher.EnableRaisingEvents = true;
}
}
答案 18 :(得分:1)
对于严肃的挖掘感到抱歉,但是我已经和这个问题争论了一段时间,最后想出办法来处理这些多次被解雇的事件。我要感谢这个主题中的每个人,因为我在讨论这个问题时已经在许多参考文献中使用过它。
这是我的完整代码。它使用字典来跟踪上次写入文件的日期和时间。它会比较该值,如果相同,则会抑制事件。然后它在启动新线程后设置值。
using System.Threading; // used for backgroundworker
using System.Diagnostics; // used for file information
private static IDictionary<string, string> fileModifiedTable = new Dictionary<string, string>(); // used to keep track of our changed events
private void fswFileWatch_Changed( object sender, FileSystemEventArgs e )
{
try
{
//check if we already have this value in our dictionary.
if ( fileModifiedTable.TryGetValue( e.FullPath, out sEmpty ) )
{
//compare timestamps
if ( fileModifiedTable[ e.FullPath ] != File.GetLastWriteTime( e.FullPath ).ToString() )
{
//lock the table
lock ( fileModifiedTable )
{
//make sure our file is still valid
if ( File.Exists( e.FullPath ) )
{
// create a new background worker to do our task while the main thread stays awake. Also give it do work and work completed handlers
BackgroundWorker newThreadWork = new BackgroundWorker();
newThreadWork.DoWork += new DoWorkEventHandler( bgwNewThread_DoWork );
newThreadWork.RunWorkerCompleted += new RunWorkerCompletedEventHandler( bgwNewThread_RunWorkerCompleted );
// capture the path
string eventFilePath = e.FullPath;
List<object> arguments = new List<object>();
// add arguments to pass to the background worker
arguments.Add( eventFilePath );
arguments.Add( newEvent.File_Modified );
// start the new thread with the arguments
newThreadWork.RunWorkerAsync( arguments );
fileModifiedTable[ e.FullPath ] = File.GetLastWriteTime( e.FullPath ).ToString(); //update the modified table with the new timestamp of the file.
FILE_MODIFIED_FLAG.WaitOne(); // wait for the modified thread to complete before firing the next thread in the event multiple threads are being worked on.
}
}
}
}
}
catch ( IOException IOExcept )
{
//catch any errors
postError( IOExcept, "fswFileWatch_Changed" );
}
}
答案 19 :(得分:1)
我改变了监视目录中文件的方式。而不是使用FileSystemWatcher我在另一个线程上轮询位置,然后查看文件的LastWriteTime。
DateTime lastWriteTime = File.GetLastWriteTime(someFilePath);
使用此信息并保留文件路径的索引及其最新写入时间,我可以确定已更改的文件或已在特定位置创建的文件。这使我免于FileSystemWatcher的奇怪之处。主要缺点是您需要一个数据结构来存储LastWriteTime和对文件的引用,但它可靠且易于实现。
答案 20 :(得分:1)
您可以尝试打开它进行写入,如果成功,则可以假设其他应用程序已完成该文件。
private void OnChanged(object source, FileSystemEventArgs e)
{
try
{
using (var fs = File.OpenWrite(e.FullPath))
{
}
//do your stuff
}
catch (Exception)
{
//no write access, other app not done
}
}
只是打开它进行写入似乎不会引发已更改的事件。所以它应该是安全的。
答案 21 :(得分:1)
FileReadTime = DateTime.Now;
private void File_Changed(object sender, FileSystemEventArgs e)
{
var lastWriteTime = File.GetLastWriteTime(e.FullPath);
if (lastWriteTime.Subtract(FileReadTime).Ticks > 0)
{
// code
FileReadTime = DateTime.Now;
}
}
答案 22 :(得分:0)
我简单地添加一个伪造支票,如下所示:
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);
}
答案 23 :(得分:0)
解决方案实际上取决于用例。您是否在留意不会更改的新文件,或者是每隔一段时间更改一次且经常更改的文件?就我而言,它不会经常更改,我不想错过任何这些更改。
但我也不想要写过程还没有写完的变化事件。
就我而言,我注意到在编写 125 个字符的 txt 文件时发生了 6 个(六个!!)onchange 事件。
我的解决方案是轮询(通常被视为负面)和变更事件的混合。正常轮询很慢,比如每 10 秒一次,以防万一 FileSystemWatcher (FSW) “错过”一个事件。轮询会立即响应 FSW 更改事件。
诀窍在于,在 FSW.Change 事件中,轮询速度更快,例如每 100 毫秒一次,并等待文件稳定。所以我们有“两阶段轮询”:阶段 1 很慢,但会立即响应 FSW 文件更改事件。阶段 2 很快,等待一个稳定的文件。
如果 FSW 检测到多个文件更改,这些事件中的每一个都会加速轮询循环,并将有效地开始一个新的、较短的等待周期。只有在轮询循环检测到文件上次写入时间没有进一步变化后,才假定文件稳定,并且您的代码可以处理更改的文件。
我选择了 10 秒和 100 毫秒的超时值,但您的用例可能需要不同的超时值。
这是轮询,其中 AppConfig.fiIO
是要注意的 FileInfo
:
private readonly EventWaitHandle ewhTimeout = new AutoResetEvent(false);
private void TwoPhasedPolling()
{
bool WaitForChange = true; //false: wait until stable
DateTime LastWriteTime = DateTime.MinValue;
while (true)
{
// wait for next poll (timeout), or FSW event
bool GotOne = ewhTimeout.WaitOne(WaitForChange ? 10 * 1000 : 100);
if (GotOne)
{
// WaitOne interrupted: end of Phase1: FSW detected file change
WaitForChange = false;
}
else
{
// WaitOne timed out: Phase2: check file write time for change
if (AppConfig.fiIO.LastWriteTime > LastWriteTime)
{
LastWriteTime = AppConfig.fiIO.LastWriteTime;
}
else
{
// End of Phase2: file has changed and is stable
WaitForChange = true;
// action on changed file
... your code here ...
}}}}
private void fileSystemWatcher1_Changed(object sender, FileSystemEventArgs e)
{
ewhTimeout.Set();
}
注意:是的,我也不喜欢 }}}}
,但它使列表更短,因此您不必滚动:-)
答案 24 :(得分:0)
我认为解决此问题的最佳解决方案是使用反应式扩展 将事件转换为可观察的事件时,只需添加Throttling(..)(最初称为Debounce(..))
此处提供示例代码
var templatesWatcher = new FileSystemWatcher(settingsSnapshot.Value.TemplatesDirectory)
{
NotifyFilter = NotifyFilters.LastWrite,
IncludeSubdirectories = true
};
templatesWatcher.EnableRaisingEvents = true;
Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
addHandler => templatesWatcher.Changed += addHandler,
removeHandler => templatesWatcher.Changed -= removeHandler)
.Throttle(TimeSpan.FromSeconds(5))
.Subscribe(args =>
{
_logger.LogInformation($"Template file {args.EventArgs.Name} has changed");
//TODO do something
});
答案 25 :(得分:0)
这是另一种方法。现在,除了传播最后一个事件之外,所有事件都被抑制,而不是传播事件的快速连续的第一个事件并抑制所有后续事件。我认为可以从这种方法中受益的场景更为常见。
要实现这一点,我们必须使用滑动延迟。每个传入事件都会取消触发前一个事件的计时器,然后重新启动计时器。这打开了永无止境的一系列事件将永远延迟传播的可能性。为简单起见,下面的扩展方法中没有针对这种异常情况的规定。
public static class FileSystemWatcherExtensions
{
public static IDisposable OnAnyEvent(this FileSystemWatcher source,
WatcherChangeTypes changeTypes, FileSystemEventHandler handler, int delay)
{
var cancellations = new Dictionary<string, CancellationTokenSource>(
StringComparer.OrdinalIgnoreCase);
var locker = new object();
if (changeTypes.HasFlag(WatcherChangeTypes.Created))
source.Created += FileSystemWatcher_Event;
if (changeTypes.HasFlag(WatcherChangeTypes.Deleted))
source.Deleted += FileSystemWatcher_Event;
if (changeTypes.HasFlag(WatcherChangeTypes.Changed))
source.Changed += FileSystemWatcher_Event;
if (changeTypes.HasFlag(WatcherChangeTypes.Renamed))
source.Renamed += FileSystemWatcher_Event;
return new Disposable(() =>
{
source.Created -= FileSystemWatcher_Event;
source.Deleted -= FileSystemWatcher_Event;
source.Changed -= FileSystemWatcher_Event;
source.Renamed -= FileSystemWatcher_Event;
});
async void FileSystemWatcher_Event(object sender, FileSystemEventArgs e)
{
var key = e.FullPath;
var cts = new CancellationTokenSource();
lock (locker)
{
if (cancellations.TryGetValue(key, out var existing))
{
existing.Cancel();
}
cancellations[key] = cts;
}
try
{
await Task.Delay(delay, cts.Token);
// Omitting ConfigureAwait(false) is intentional here.
// Continuing in the captured context is desirable.
}
catch (TaskCanceledException)
{
return;
}
lock (locker)
{
if (cancellations.TryGetValue(key, out var existing)
&& existing == cts)
{
cancellations.Remove(key);
}
}
cts.Dispose();
handler(sender, e);
}
}
public static IDisposable OnAllEvents(this FileSystemWatcher source,
FileSystemEventHandler handler, int delay)
=> OnAnyEvent(source, WatcherChangeTypes.All, handler, delay);
public static IDisposable OnCreated(this FileSystemWatcher source,
FileSystemEventHandler handler, int delay)
=> OnAnyEvent(source, WatcherChangeTypes.Created, handler, delay);
public static IDisposable OnDeleted(this FileSystemWatcher source,
FileSystemEventHandler handler, int delay)
=> OnAnyEvent(source, WatcherChangeTypes.Deleted, handler, delay);
public static IDisposable OnChanged(this FileSystemWatcher source,
FileSystemEventHandler handler, int delay)
=> OnAnyEvent(source, WatcherChangeTypes.Changed, handler, delay);
public static IDisposable OnRenamed(this FileSystemWatcher source,
FileSystemEventHandler handler, int delay)
=> OnAnyEvent(source, WatcherChangeTypes.Renamed, handler, delay);
private struct Disposable : IDisposable
{
private readonly Action _action;
internal Disposable(Action action) => _action = action;
public void Dispose() => _action?.Invoke();
}
}
用法示例:
myWatcher.OnAnyEvent(WatcherChangeTypes.Created | WatcherChangeTypes.Changed,
MyFileSystemWatcher_Event, 100);
此行将对两个事件的订阅结合在一起,即Created
和Changed
。因此,大致相当于这些:
myWatcher.Created += MyFileSystemWatcher_Event;
myWatcher.Changed += MyFileSystemWatcher_Event;
区别在于,这两个事件被视为单一事件类型,并且在这些事件快速连续的情况下,只会传播最后一个事件。例如,如果一个Created
事件之后是两个Changed
事件,并且这三个事件之间的时间间隔不大于100毫秒,则通过调用仅传播第二个Changed
事件MyFileSystemWatcher_Event
处理程序,之前的处理程序将被丢弃。
答案 26 :(得分:0)
我只想对最后一个事件做出反应,以防万一,在Linux文件更改时,似乎该文件在第一次调用时为空,然后在下一次调用时再次填充,并且不介意浪费一些时间如果操作系统决定做一些文件/属性更改。
我在这里使用.NET异步来帮助我进行线程化。
private static int _fileSystemWatcherCounts;
private async void OnChanged(object sender, FileSystemEventArgs e)
{
// Filter several calls in short period of time
Interlocked.Increment(ref _fileSystemWatcherCounts);
await Task.Delay(100);
if (Interlocked.Decrement(ref _fileSystemWatcherCounts) == 0)
DoYourWork();
}
答案 27 :(得分:0)
就我而言,插入完成后,需要获取其他应用程序插入的文本文件的最后一行。这是我的解决方案。引发第一个事件时,我禁止观察者引发其他事件,然后调用计时器TimeElapsedEvent,因为调用我的句柄函数OnChanged时,我需要文本文件的大小,但当时的大小不是实际大小,它是插入之前文件的大小。因此,我等待一段时间以继续正确的文件大小。
private FileSystemWatcher watcher = new FileSystemWatcher();
...
watcher.Path = "E:\\data";
watcher.NotifyFilter = NotifyFilters.LastWrite ;
watcher.Filter = "data.txt";
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = true;
...
private void OnChanged(object source, FileSystemEventArgs e)
{
System.Timers.Timer t = new System.Timers.Timer();
try
{
watcher.Changed -= new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = false;
t.Interval = 500;
t.Elapsed += (sender, args) => t_Elapsed(sender, e);
t.Start();
}
catch(Exception ex) {
;
}
}
private void t_Elapsed(object sender, FileSystemEventArgs e)
{
((System.Timers.Timer)sender).Stop();
//.. Do you stuff HERE ..
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = true;
}
答案 28 :(得分:0)
很多这些答案都令人震惊,真的。 来自我的XanderUI控件库的一些代码修复了这个问题。
private void OnChanged(object sender, FilesystemEventArgs e)
{
if (FSWatcher.IncludeSubdirectories == true)
{
if (File.Exists(e.FullPath)) { DO YOUR FILE CHANGE STUFF HERE... }
}
else DO YOUR DIRECTORY CHANGE STUFF HERE...
}
答案 29 :(得分:0)
我接近了像这样的双重创建问题,忽略了第一个事件:
Private WithEvents fsw As New System.IO.FileSystemWatcher
Private complete As New List(Of String)
Private Sub fsw_Created(ByVal sender As Object, _
ByVal e As System.IO.FileSystemEventArgs) Handles fsw.Created
If Not complete.Contains(e.FullPath) Then
complete.Add(e.FullPath)
Else
complete.Remove(e.FullPath)
Dim th As New Threading.Thread(AddressOf hprocess)
th.Start(e)
End If
End Sub
答案 30 :(得分:0)
我必须结合上面帖子中的几个想法并添加文件锁定检查以使其适用于我:
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
}
}
答案 31 :(得分:0)
试试这个!
string temp="";
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)
{
.......
if(temp=="")
{
//do thing you want.
temp = e.name //name of text file.
}else if(temp !="" && temp != e.name)
{
//do thing you want.
temp = e.name //name of text file.
}else
{
//second fire ignored.
}
}
答案 32 :(得分:0)
此解决方案适用于我的生产应用程序:
环境:
VB.Net Framework 4.5.2
设置手动对象属性:NotifyFilter = Size
然后使用此代码:
Public Class main
Dim CalledOnce = False
Private Sub FileSystemWatcher1_Changed(sender As Object, e As IO.FileSystemEventArgs) Handles FileSystemWatcher1.Changed
If (CalledOnce = False) Then
CalledOnce = True
If (e.ChangeType = 4) Then
' Do task...
CalledOnce = False
End If
End Sub
End Sub
答案 33 :(得分:0)
我能够通过添加一个检查缓冲区数组中重复项的函数来做到这一点。
然后使用计时器在数组未被修改X时间后执行操作: - 每次将某些内容写入缓冲区时重置计时器 - 在刻度线上执行操作
这也会捕获另一种重复类型。如果修改文件夹中的文件,该文件夹也会抛出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
答案 34 :(得分:-1)
曾经寻找答案,但我想出了一个肮脏的解决方案。由于我的事件触发了两次,因此第二步操作什么也没做。
$count = 1
$action = {
if($count -eq 1){
#DO SOMETHING
$count = 2
}else{
$count = 1
}
}
答案 35 :(得分:-1)
好吧。 这个问题很老,我今天在这里...但是最后我使用了一种更简单的方法。
我希望我贡献了一些东西=)
答案 36 :(得分:-1)
在与Changed事件一起触发两次后,我发现最简单的解决方案是比较事件触发前后文件的byte []内容,然后仅在文件内容实际发生时才执行代码改变了。
基本问题是,除了内容更改外,对文件的每种特定更改类型都会触发同一事件。仅保存对文件内容的更改会触发该事件多次(由于文件日期,属性或它正在检测的其他任何更改)。
FileSystemWatcher类应该将为文件或目录可能经历的所有各种变化提供更多事件。 .NET团队对此进行了烦人的监督。荒谬的是,系统内或用户的单个动作将导致在.NET中触发单个事件多次。这是一个非常愚蠢的设计。
在你们当中的一个辩护律师提出一些绝对荒谬的借口之前,为什么 WE 都能够编写自定义代码来防止事件多次触发,但是Microsoft的开发人员却不能,这样可以节省您的时间。这已成为StackOverflow体验中最糟糕的部分:所有拒绝接吻的辩护者都拒绝承认我们周围每天都在使用软件中存在的设计缺陷,如果发布者仅花费时间和金钱,就可以纠正这些缺陷。
答案 37 :(得分:-2)
如果您注册OnChanged事件,那么在更改之前删除受监控的文件可能会有效,只要您只需要监控OnChange事件..
答案 38 :(得分:-6)
嗯,这是我的解决方案,如何只举一次事件:
FileSystemWatcheк watcher = new FileSystemWatcher();
//'path' - path to the file that has been modified.
watcher.Changed += (s, e) => FileChanged(path);
这里是FileChanged的实现
//count is our counter to triger when we can raise and when not.
private int count = 0;
private void FileChanged(string path)
{
if (count % 2 == 0)
{
//code here
}
count ++;
}
答案 39 :(得分:-6)
简单定义一个全局变量var1 = true
。
Private Sub FileWatchman_Changed(ByVal sender As System.Object, ByVal e As System.IO.FileSystemEventArgs) Handles FileWatchman.Changed
If var1 = true
your logic goes here
var1 = false
Else
var1 = true
End If
End Sub