如何避免C#中的FileSystemWatcher错误?
目录
中一次发生太多变化
我必须检测网络共享上的所有更改。 InternalBufferSize增加到8192 * 128
答案 0 :(得分:7)
你应该做两件事:
InternalBufferSize
设置为支持的最大值(65536)。您尝试将其设置为" 8192 * 128"大于documentation中列出的最大支持值,因此您可能根本没有增加缓冲区大小。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上游可能更慢),这里还有进一步优化的空间。只要您的调度员超过了临界点"但是,如果发件人无法比发送事件更快地发布事件,那么您永远不会得到积压,因此永远不会破坏缓冲区。我相信这种方法会让你进入安全区域。
答案 1 :(得分:4)
我想我可能已经找到了一种可以帮助大大改善缓冲区使用的模式。
此类的问题是,在事件的委托完成运行之前,它无法释放用于保存该信息的内存。
对于我的生活,我不知道为什么最大的InternalBufferSize被设置为64Kb,但有了这个想法,你将更有效地使用那个小缓冲区。
如果不是在代理中进行操作,而是简单地对它们进行排队并将其执行推迟给后台工作程序,那么它所使用的内存量将会相当小。
事实上,我不知道为什么这个类本身并没有首先实现这样的东西。
下面的代码只是这个想法的一段代码示例,不应该在生产环境中使用,但它增加了我可以复制和跟踪的文件数量。
using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.IO;
using System.Threading;
using NUnit.Framework;
namespace Soundnet.Synchronisation.FileSystemWatcherTests
{
/// <summary>
///
/// </summary>
[TestFixture]
public class Tests
{
static readonly ConcurrentQueue<Change> ChangesQueue = new ConcurrentQueue<Change>();
const string Destination = @"c:\Destination";
const string Source = @"c:\Source";
/// <summary>
/// Tests this instance.
/// </summary>
[Test]
public void Test()
{
var changesBackgroundWorker = new BackgroundWorker();
changesBackgroundWorker.DoWork += ChangesBackgroundWorkerOnDoWork;
changesBackgroundWorker.RunWorkerAsync();
var fileSystemWatcher = new FileSystemWatcher
{
Path = Source,
EnableRaisingEvents = true,
IncludeSubdirectories = true,
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.LastAccess | NotifyFilters.CreationTime | NotifyFilters.FileName | NotifyFilters.DirectoryName,
InternalBufferSize = 65536
};
fileSystemWatcher.Created += FileSystemWatcherOnCreated;
fileSystemWatcher.Deleted += FileSystemWatcherOnDeleted;
fileSystemWatcher.Renamed += FileSystemWatcherOnRenamed;
fileSystemWatcher.Error += FileSystemWatcherOnError;
while (true)
Thread.Sleep(1000000);
}
/// <summary>
/// Changeses the background worker configuration document work.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="doWorkEventArgs">The <see cref="DoWorkEventArgs"/> instance containing the event data.</param>
/// <exception cref="System.ArgumentOutOfRangeException"></exception>
private static void ChangesBackgroundWorkerOnDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
{
while (true)
{
Change change;
if (ChangesQueue.TryDequeue(out change))
{
var backgroundWorker = new BackgroundWorker();
switch (change.ChangeType)
{
case WatcherChangeTypes.Created:
backgroundWorker.DoWork += (o, args) =>
{
var fileSystemType = GetFileSystemType(change.FullPath);
var newItem = Path.Combine(Destination, change.Name);
while (true)
{
try
{
switch (fileSystemType)
{
case FileSystemType.File:
File.Copy(change.FullPath, newItem, true);
break;
case FileSystemType.Directory:
var directorySecurity =
Directory.GetAccessControl(change.FullPath);
Directory.CreateDirectory(newItem, directorySecurity);
break;
case FileSystemType.NotExistant:
break;
}
return;
}
catch (IOException exception)
{
Thread.Sleep(100);
Console.WriteLine(exception.Message);
}
}
};
break;
case WatcherChangeTypes.Deleted:
backgroundWorker.DoWork += (o, args) =>
{
var itemToDelete = Path.Combine(Destination, change.Name);
var fileSystemType = GetFileSystemType(itemToDelete);
switch (fileSystemType)
{
case FileSystemType.File:
File.Delete(itemToDelete);
break;
case FileSystemType.Directory:
Directory.Delete(itemToDelete, true);
break;
}
};
break;
case WatcherChangeTypes.Changed:
backgroundWorker.DoWork += (o, args) =>
{
var fileSystemType = GetFileSystemType(change.FullPath);
var newItem = Path.Combine(Destination, change.Name);
switch (fileSystemType)
{
case FileSystemType.File:
File.Copy(change.FullPath, newItem, true);
break;
}
};
break;
case WatcherChangeTypes.Renamed:
backgroundWorker.DoWork += (o, args) =>
{
var fileSystemType = GetFileSystemType(change.FullPath);
var oldItem = Path.Combine(Destination, change.OldName);
var newItem = Path.Combine(Destination, change.Name);
switch (fileSystemType)
{
case FileSystemType.File:
if (File.Exists(oldItem))
File.Move(oldItem, newItem);
break;
case FileSystemType.Directory:
if (Directory.Exists(oldItem))
Directory.Move(oldItem, newItem);
break;
}
};
break;
case WatcherChangeTypes.All:
break;
default:
throw new ArgumentOutOfRangeException();
}
backgroundWorker.RunWorkerAsync();
}
}
}
/// <summary>
/// Files the system watcher configuration created.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="fileSystemEventArgs">The <see cref="FileSystemEventArgs"/> instance containing the event data.</param>
private static void FileSystemWatcherOnCreated(object sender, FileSystemEventArgs fileSystemEventArgs)
{
ChangesQueue.Enqueue(new Change
{
ChangeType = WatcherChangeTypes.Created,
FullPath = fileSystemEventArgs.FullPath,
Name = fileSystemEventArgs.Name
});
}
/// <summary>
/// Files the system watcher configuration deleted.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="fileSystemEventArgs">The <see cref="FileSystemEventArgs"/> instance containing the event data.</param>
private static void FileSystemWatcherOnDeleted(object sender, FileSystemEventArgs fileSystemEventArgs)
{
ChangesQueue.Enqueue(new Change
{
ChangeType = WatcherChangeTypes.Deleted,
FullPath = fileSystemEventArgs.FullPath,
Name = fileSystemEventArgs.Name
});
}
/// <summary>
/// Files the system watcher configuration error.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="errorEventArgs">The <see cref="ErrorEventArgs"/> instance containing the event data.</param>
private static void FileSystemWatcherOnError(object sender, ErrorEventArgs errorEventArgs)
{
var exception = errorEventArgs.GetException();
Console.WriteLine(exception.Message);
}
/// <summary>
/// Files the system watcher configuration renamed.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="fileSystemEventArgs">The <see cref="RenamedEventArgs"/> instance containing the event data.</param>
private static void FileSystemWatcherOnRenamed(object sender, RenamedEventArgs fileSystemEventArgs)
{
ChangesQueue.Enqueue(new Change
{
ChangeType = WatcherChangeTypes.Renamed,
FullPath = fileSystemEventArgs.FullPath,
Name = fileSystemEventArgs.Name,
OldFullPath = fileSystemEventArgs.OldFullPath,
OldName = fileSystemEventArgs.OldName
});
}
/// <summary>
/// Gets the type of the file system.
/// </summary>
/// <param name="fullPath">The full path.</param>
/// <returns></returns>
private static FileSystemType GetFileSystemType(string fullPath)
{
if (Directory.Exists(fullPath))
return FileSystemType.Directory;
if (File.Exists(fullPath))
return FileSystemType.File;
return FileSystemType.NotExistant;
}
}
/// <summary>
/// Type of file system object
/// </summary>
internal enum FileSystemType
{
/// <summary>
/// The file
/// </summary>
File,
/// <summary>
/// The directory
/// </summary>
Directory,
/// <summary>
/// The not existant
/// </summary>
NotExistant
}
/// <summary>
/// Change information
/// </summary>
public class Change
{
/// <summary>
/// Gets or sets the type of the change.
/// </summary>
/// <value>
/// The type of the change.
/// </value>
public WatcherChangeTypes ChangeType { get; set; }
/// <summary>
/// Gets or sets the full path.
/// </summary>
/// <value>
/// The full path.
/// </value>
public string FullPath { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>
/// The name.
/// </value>
public string Name { get; set; }
/// <summary>
/// Gets or sets the old full path.
/// </summary>
/// <value>
/// The old full path.
/// </value>
public string OldFullPath { get; set; }
/// <summary>
/// Gets or sets the old name.
/// </summary>
/// <value>
/// The old name.
/// </value>
public string OldName { get; set; }
}
}
答案 2 :(得分:2)
我有经验,FileSystemWatcher并不总是最可靠的使用方法。您可以指定过滤器以缩小正在观察的文件(NotifyFilter),或者增加缓冲区大小。
但是根据您的要求,您可能还想以另一种方式进行操作,例如每隔x秒轮询一次以获取文件列表。但是,您可能需要告诉我们有关您的业务案例的更多信息。
答案 3 :(得分:2)
来自MSDN
;
Windows操作系统会通知组件文件更改 在
FileSystemWatcher
创建的缓冲区中。如果有很多 在短时间内发生变化,缓冲区可能会溢出。这导致了 组件忘记跟踪目录中的更改,它只会 提供全面通知。增加缓冲区的大小InternalBufferSize
属性价格昂贵,因为它来自 非分页内存,无法换出磁盘,所以保持 缓冲区虽小但足够大,不会错过任何文件更改事件。 要避免缓冲区溢出,请使用NotifyFilter
和IncludeSubdirectories
属性,以便您可以过滤掉不需要的更改 通知。
答案 4 :(得分:0)
SHChangeNotifyRegister可用于获取shell通知。
答案 5 :(得分:0)
如果增加缓冲区大小应该修复,但它不是一个实际的解决方案。 因为要确保它始终记录使缓冲区变大所需的一切。 这将极大地影响性能。 我认为通过实现多线程可以解决性能问题。