我有一个应用程序,我正在寻找一个文本文件,如果对文件做了任何更改,我使用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并保存它。


当前回答

代码可自定义禁用阻止第二个观察者升起的时间间隔,如果他们存在,则不阻止观察者:

    namespace Watcher
    {
        class Static
        {
            public static DateTime lastDomain { get; set; }
            public static string lastDomainStr { get; set; }
        }
        public partial class Form1 : Form
       {
            int minMs = 20;//time for blocking in ms
            public Form1()
            {
                InitializeComponent();
                Static.lastDomain = new DateTime(1970, 1, 1, 0, 0, 0);
                Static.lastDomainStr = "";  
                Start();
            }
             private void Start()//Start watcher
             {
                //...
                domain.Changed += new FileSystemEventHandler(Domain);
                domain.EnableRaisingEvents = true;
                //...you second unblocked watchers
                second.Changed += new FileSystemEventHandler(Second);
                second.EnableRaisingEvents = true;
             }
             private void Domain(object source, FileSystemEventArgs e)
             {
                if (now.Subtract(Static.lastDomain).TotalMilliseconds < minMs && Static.lastDomainStr == e.FullPath)return;
                 //...you code here
                 /* if you need form access
                 this.Invoke(new MethodInvoker(() =>{ textBox1.Text = "...";}));
                 */
                 Static.lastDomain = DateTime.Now;
                 Static.lastDomainStr = e.FullPath;
             }
             private void Second(object source, FileSystemEventArgs e)
             {
                  //...Second rised
             }
       }
    }

其他回答

我已经用一个类创建了一个Git repo,该类扩展了FileSystemWatcher,以便仅在复制完成时触发事件。它将丢弃除最后一个事件以外的所有已更改事件,仅在文件可读时才引发该事件。

下载FileSystemSafeWatcher并将其添加到项目中。

然后将其用作普通的FileSystemWatcher并在事件触发时进行监视。

var fsw = new FileSystemSafeWatcher(file);
fsw.EnableRaisingEvents = true;
// Add event handlers here
fsw.Created += fsw_Created;

我改变了监视目录中的文件的方式。我没有使用FileSystemWatcher,而是在另一个线程上轮询位置,然后查看文件的LastWriteTime。

DateTime lastWriteTime = File.GetLastWriteTime(someFilePath);

使用这些信息并保持文件路径的索引和最近的写入时间,我可以确定在特定位置已更改或已创建的文件。这使我摆脱了FileSystemWatcher的奇怪之处。主要的缺点是您需要一个数据结构来存储LastWriteTime和对文件的引用,但是它是可靠且易于实现的。

这是另一种方法。现在,除了最后一个事件以外,所有事件都被抑制,而不是传播一系列事件中的第一个事件并抑制所有接下来的事件。我认为可以从这种方法中受益的场景更常见。

要做到这一点,我们必须使用滑动延迟。每个传入事件都会取消触发前一个事件的计时器,并重新启动计时器。这开启了一种可能性,即一系列永无止境的事件将永远推迟传播。为了简单起见,在下面的扩展方法中没有针对这种异常情况的规定。

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 msec,则只有第二个Changed事件将通过调用MyFileSystemWatcher_Event处理程序来传播,而前一个事件将被丢弃。

我花了大量时间使用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.
}

解决方案实际上取决于用例。您是否在注意不更改的新文件,或每隔一段时间更改一次的文件或经常更改的文件?在我的情况下,它的变化不太频繁,我不想错过任何这些变化。

但是我也不想在写入过程尚未完成写入的地方发生更改事件。

在我的情况下,我注意到6 (6 !!)onchange事件写一个125字符的txt文件。

我的解决方案是民意调查和改变事件的混合,民意调查经常被消极地看待。正常轮询比较慢,比如每10秒一次,以防FileSystemWatcher (FSW)“错过”一个事件。轮询立即响应FSW更改事件。

关键是在FSW。更改事件时,轮询速度加快,例如每100毫秒,并等待直到文件稳定。因此我们有了“两阶段轮询”:阶段1比较慢,但在FSW文件更改事件时立即响应。第二阶段是快速的,等待一个稳定的文件。

如果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();
}

NB:是的,我也不喜欢}}}},但它使列表更短,这样你就不必滚动了:-)