您应该做两件事:
- Set
InternalBufferSize
到支持的最大值 (65536)。您尝试将其设置为“8192 * 128”大于中列出的最大支持值文档 https://msdn.microsoft.com/en-us/library/system.io.filesystemwatcher.internalbuffersize(v=vs.110).aspx,因此您可能根本没有增加缓冲区大小。
- 将事件排队
FileSystemWatcher
到后台线程进行处理。
这是这里的第二点,还没有被很好地理解,确实应该在 MSDN 上记录下来。在内部,FileSystemWatcher
正在将更改事件排队到您设置上面大小的内部缓冲区中。但重要的是,项目仅从该缓冲区中删除事件处理程序返回后。这意味着事件处理程序引入的每个周期的开销都会增加缓冲区填满的可能性。你应该做的是清除有限的队列FileSystemWatcher
尽快,并将事件移动到您自己的无限队列中,以您可以处理的速度进行处理,或者如果您愿意的话丢弃,但需要一些智能。
这基本上是我在代码中所做的事情。首先,我启动自己的调度程序线程:
Dispatcher changeDispatcher = null;
ManualResetEvent changeDispatcherStarted = new ManualResetEvent(false);
Action changeThreadHandler = () =>
{
changeDispatcher = Dispatcher.CurrentDispatcher;
changeDispatcherStarted.Set();
Dispatcher.Run();
};
new Thread(() => changeThreadHandler()) { IsBackground = true }.Start();
changeDispatcherStarted.WaitOne();
然后我创建观察者。请注意所设置的缓冲区大小。就我而言,我只观察目标目录中的更改,而不是子目录中的更改:
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = path;
watcher.InternalBufferSize = 64 * 1024;
watcher.IncludeSubdirectories = false;
现在我附加了事件处理程序,但在这里我将它们调用到调度程序上,而不是在观察程序线程中同步运行它们。是的,调度程序将按顺序处理事件:
watcher.Changed += (sender, e) => changeDispatcher.BeginInvoke(new Action(() => OnChanged(sender, e)));
watcher.Created += (sender, e) => changeDispatcher.BeginInvoke(new Action(() => OnCreated(sender, e)));
watcher.Deleted += (sender, e) => changeDispatcher.BeginInvoke(new Action(() => OnDeleted(sender, e)));
watcher.Renamed += (sender, e) => changeDispatcher.BeginInvoke(new Action(() => OnRenamed(sender, e)));
最后,在处理完之后FileSystemWatcher
(你正在这样做,对吧?),你需要关闭你的调度程序:
watcher.Dispose()
changeDispatcher.BeginInvokeShutdown(DispatcherPriority.Normal);
就是这样。我自己在网络和本地场景中都遇到了这个问题。使用这种方法后,即使尽快将空文件敲击到监视目录中,我也无法再次生成此错误。如果在这种情况下您确实设法以某种方式耗尽缓冲区(我不确定这是否可能,API 上游可能会更慢),那么这里仍然有进一步优化的空间。只要您的调度程序超过了“临界点”,即发送者发布事件的速度不能比您调度事件的速度快,您就永远不会遇到积压,因此永远不会耗尽缓冲区。我相信这种方法可以让你进入安全区。