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


当前回答

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

其他回答

试试这个!

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.
}

}

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

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

在我的情况下,我注意到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:是的,我也不喜欢}}}},但它使列表更短,这样你就不必滚动了:-)

我简单地添加了一个dupe检查如下:

 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);
    }

这已经很晚了,但我最近遇到了这个问题,然后我想发表我的一点贡献。

首先,许多建议的解决方案都适用于单个更新的文件,而我需要在较短的时间内(几十毫秒)收到关于2-3个更改文件的通知,而重复时间相对较长(几十秒到几分钟)。

早期建议的最有趣的链接之一是FileSystemWatcher is a Bit Broken。然而,所提出的解决方案只是部分工作,正如同一作者在。net MemoryCache Expiration Demystified的不稳定行为中指出的那样,即使在20秒后也会发出通知。

然后我所做的是基于类似的原则设计一个愚蠢的替代解决方案,没有MemoryCache。

基本上,它创建了一个List<>的项目,其中有一个Key,它是文件的完整路径和一个过期计时器。如果另一个事件再次触发该更改,则在列表中找到该元素,计时器将更新为新的过期时间。 根据经验,过期时间足够长,足以在单个OnStableChange通知中收集多个事件,而不会太长,以至于感觉没有响应。

当你实例化Whatever时,你也将它链接到一个目录和一个非常基本的外部回调。

没有什么是真正优化的,我只是在几行中寻找一个解决方案。

我把它发表在这里

对我来说,这样你就可以在另一个应用程序上验证 某个更聪明、更有经验的人可以改进它,并帮助我了解它的不足之处

    internal class Whatever
    {
        private FileSystemWatcher? watcher = null;

        public delegate void DelegateFileChange(string path);
        public DelegateFileChange? onChange;

        private const int CacheTimeMilliseconds = 200;

        private class ChangeItem
        {
            public delegate void DelegateChangeItem(string key);
            public string Key { get; set; } = "";
            public System.Timers.Timer Expiration = new();
            public DelegateChangeItem? SignalChanged = null;
        }
        private class ChangeCache
        {
            private readonly List<ChangeItem> _changes = new();

            public void Set(string key, int milliSecs, ChangeItem.DelegateChangeItem? signal = null)
            {
                lock (_changes)
                {
                    ChangeItem? existing = _changes.Find(item => item.Key == key);
                    if (existing != null)
                    {
                        existing.Expiration.Interval = milliSecs;
                        existing.SignalChanged = signal;
                    }
                    else
                    {
                        ChangeItem change = new()
                        {
                            Key = key,
                            SignalChanged = signal
                        };
                        change.Expiration.Interval = milliSecs;
                        change.Expiration.AutoReset = false;
                        change.Expiration.Elapsed += delegate { Change_Elapsed(key); };
                        change.Expiration.Enabled = true;
                        _changes.Add(change);
                    }
                }
            }

            private void Change_Elapsed(string key)
            {
                lock (_changes)
                {
                    ChangeItem? existing = _changes.Find(item => item.Key == key);
                    existing?.SignalChanged?.Invoke(key);
                    _changes.RemoveAll(item => item.Key == key);
                }
            }
        }

        private ChangeCache changeCache = new();

        public bool Link(string directory, DelegateFileChange? fileChange = null)
        {
            bool result = false;

            try
            {
                if (Directory.Exists(directory))
                {
                    watcher = new FileSystemWatcher(directory);
                    watcher.NotifyFilter = NotifyFilters.LastWrite;
                    watcher.Changed += Watcher_Changed;

                    onChange = fileChange;

                    watcher.Filter = "*.*";
                    watcher.IncludeSubdirectories = true;
                    watcher.EnableRaisingEvents = true;

                    result = true;
                }
            }
            catch (Exception)
            {
            }

            return result;
        }

        private void OnStableChange(string path)
        {
            if (File.Exists(path))
            {
                onChange?.Invoke(path);
            }
        }

        public void Watcher_Changed(object sender, FileSystemEventArgs e)
        {
            changeCache.Set(e.FullPath, CacheTimeMilliseconds, OnStableChange);
        }
    }

很抱歉挖了坟墓,但我已经与这个问题斗争了一段时间,终于想出了一种方法来处理这些多重发射事件。我想要感谢这篇文章中的每一个人,因为我在与这个问题作斗争时已经在许多参考文献中使用了它。

这是我的完整代码。它使用字典来跟踪文件最后一次写入的日期和时间。它比较该值,如果值相同,则抑制事件。然后在启动新线程后设置该值。

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" );
        }
    }