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


当前回答

我简单地添加了一个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);
    }

其他回答

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

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

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

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

我的场景是,我有一个虚拟机,其中有一个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;
    }
}

我主要创建一个哈希表来存储文件写时间信息。然后,如果哈希表有被修改的文件路径,并且它的时间值与当前通知的文件更改相同,那么我就知道它是事件的副本,并忽略它。

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

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

以下是我的方法:

// 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是可以的,因为我更喜欢错过一些变化,而不是每秒用> 1条消息淹没邮箱。 至少在同时更改多个文件的情况下,它可以正常工作。

Another solution I've thought of would be to replace the list with a dictionary mapping files to their respective MD5, so you wouldn't have to choose an arbitrary interval since you wouldn't have to delete the entry but update its value, and cancel your stuff if it hasn't changed. It has the downside of having a Dictionary growing in memory as files are monitored and eating more and more memory, but I've read somewhere that the amount of files monitored depends on the FSW's internal buffer, so maybe not that critical. Dunno how MD5 computing time would affect your code's performances either, careful =\

一个可能的“黑客”是使用响应式扩展来限制事件,例如:

var watcher = new FileSystemWatcher("./");

Observable.FromEventPattern<FileSystemEventArgs>(watcher, "Changed")
            .Throttle(new TimeSpan(500000))
            .Subscribe(HandleChangeEvent);

watcher.EnableRaisingEvents = true;

在本例中,我将设置为50ms,在我的系统上这就足够了,但更高的值应该更安全。(就像我说的,这仍然是一个“hack”)。