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


当前回答

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

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

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

其他回答

以下是我的解决方案,帮助我阻止事件被提出两次:

watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size;

这里我只设置了文件名和大小的NotifyFilter属性。 watcher是FileSystemWatcher的对象。希望这能有所帮助。

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

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

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

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

试试下面的代码:

class WatchPlotDirectory
{
    bool let = false;
    FileSystemWatcher watcher;
    string path = "C:/Users/jamie/OneDrive/Pictures/Screenshots";

    public WatchPlotDirectory()
    {
        watcher = new FileSystemWatcher();
        watcher.Path = path;
        watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
                               | NotifyFilters.FileName | NotifyFilters.DirectoryName;
        watcher.Filter = "*.*";
        watcher.Changed += new FileSystemEventHandler(OnChanged);
        watcher.Renamed += new RenamedEventHandler(OnRenamed);
        watcher.EnableRaisingEvents = true;
    }



    void OnChanged(object sender, FileSystemEventArgs e)
    {
        if (let==false) {
            string mgs = string.Format("File {0} | {1}",
                                       e.FullPath, e.ChangeType);
            Console.WriteLine("onchange: " + mgs);
            let = true;
        }

        else
        {
            let = false;
        }


    }

    void OnRenamed(object sender, RenamedEventArgs e)
    {
        string log = string.Format("{0} | Renamed from {1}",
                                   e.FullPath, e.OldName);
        Console.WriteLine("onrenamed: " + log);

    }

    public void setPath(string path)
    {
        this.path = path;
    }
}

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

事件如果没有问,这是一个遗憾,没有现成的解决方案样本f#。 要解决这个问题,这里有我的方法,因为我可以,而且f#是一种很棒的。net语言。

使用FSharp.Control.Reactive包过滤掉重复的事件,它只是响应式扩展的f#包装器。所有这些都可以针对全框架或netstandard2.0:

let createWatcher path filter () =
    new FileSystemWatcher(
        Path = path,
        Filter = filter,
        EnableRaisingEvents = true,
        SynchronizingObject = null // not needed for console applications
    )

let createSources (fsWatcher: FileSystemWatcher) =
    // use here needed events only. 
    // convert `Error` and `Renamed` events to be merded
    [| fsWatcher.Changed :> IObservable<_>
       fsWatcher.Deleted :> IObservable<_>
       fsWatcher.Created :> IObservable<_>
       //fsWatcher.Renamed |> Observable.map renamedToNeeded
       //fsWatcher.Error   |> Observable.map errorToNeeded
    |] |> Observable.mergeArray

let handle (e: FileSystemEventArgs) =
    printfn "handle %A event '%s' '%s' " e.ChangeType e.Name e.FullPath 

let watch path filter throttleTime =
    // disposes watcher if observer subscription is disposed
    Observable.using (createWatcher path filter) createSources
    // filter out multiple equal events
    |> Observable.distinctUntilChanged
    // filter out multiple Changed
    |> Observable.throttle throttleTime
    |> Observable.subscribe handle

[<EntryPoint>]
let main _args =
    let path = @"C:\Temp\WatchDir"
    let filter = "*.zip"
    let throttleTime = TimeSpan.FromSeconds 10.
    use _subscription = watch path filter throttleTime
    System.Console.ReadKey() |> ignore
    0 // return an integer exit code