FileSystemWatcher的Changed事件被触发两次

390

我有一个应用程序,在其中寻找一个文本文件,如果文件有任何更改,我将使用 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)
{
   .......
}
在我的情况下,当我改变文本文件version.txt并保存它时,OnChanged被调用了两次。

这是一个解决方法,但应该根据解决方法的质量来评判。跟踪更改效果完美且简单。OP正在寻找一种抑制重复事件的方法,下面的回复就提供了这种方法。https://msdn.microsoft.com/en-us/library/system.io.filesystemwatcher.created.aspx解释了多个事件可能是由于反病毒软件或其他“复杂的文件系统问题”引起的(听起来只是借口)。 - Tyler Montney
2
我最近打开了这个问题 https://github.com/Microsoft/dotnet/issues/347 - Stephan Ahlf
3
我创建了一个类,可以帮助您获取一个事件。您可以从https://github.com/melenaos/FileSystemSafeWatcher获取代码。 - Menelaos Vergis
上面Menelaos Vergis提供的解决方案百分之百有效。 - undefined
45个回答

0

我们可以像这样简单地实现它。对我来说很有效。

private static void OnChanged(object sender, FileSystemEventArgs e) 
{
    if (File.GetAttributes(e.FullPath) == FileAttributes.Directory)
        return;
    double timeSpan = DateTime.Now.Subtract(File.GetLastWriteTime(e.FullPath)).TotalSeconds;
    if (timeSpan > 1)
        return;
    Console.WriteLine($"Changed: {e.FullPath}");
}

0

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

DateTime lastWriteTime = File.GetLastWriteTime(someFilePath);

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


10
你还需要燃烧后台循环,而不是被系统事件通知。 - Matthew Whited

0
我能夠透過添加一個檢查緩衝陣列中重複項的函數來完成這個任務。
然後,使用計時器在陣列在X時間內未被修改時執行動作: - 每次向緩衝區寫入內容時重置計時器 - 在定時器滴答時執行操作
這還可以捕捉另一種重複類型。如果您修改文件夾中的文件,文件夾也會觸發Change事件。
Function is_duplicate(str1 As String) As Boolean
    If lb_actions_list.Items.Count = 0 Then
        Return False
    Else
        Dim compStr As String = lb_actions_list.Items(lb_actions_list.Items.Count - 1).ToString
        compStr = compStr.Substring(compStr.IndexOf("-") + 1).Trim

        If compStr <> str1 AndAlso compStr.parentDir <> str1 & "\" Then
            Return False
        Else
            Return True
        End If
    End If
End Function

Public Module extentions
<Extension()>
Public Function parentDir(ByVal aString As String) As String
    Return aString.Substring(0, CInt(InStrRev(aString, "\", aString.Length - 1)))
End Function
End Module

0

这个解决方案在我的生产应用中有效:

环境:

VB.Net Framework 4.5.2

手动设置对象属性:NotifyFilter = Size

然后使用以下代码:

Public Class main
    Dim CalledOnce = False
    Private Sub FileSystemWatcher1_Changed(sender As Object, e As IO.FileSystemEventArgs) Handles FileSystemWatcher1.Changed
            If (CalledOnce = False) Then
                CalledOnce = True
                If (e.ChangeType = 4) Then
                    ' Do task...
                CalledOnce = False
            End If
        End Sub
End Sub

它使用与@Jamie Krcmar相同的概念,但适用于VB.NET。 - wpcoder

0

很多答案都令人震惊,实际上。 这是我XanderUI控件库中的一些代码,可以解决这个问题。

private void OnChanged(object sender, FilesystemEventArgs e)
{
    if (FSWatcher.IncludeSubdirectories == true)
    {
        if (File.Exists(e.FullPath)) { DO YOUR FILE CHANGE STUFF HERE... }
    }
    else DO YOUR DIRECTORY CHANGE STUFF HERE...
}

0

我像这样处理双重创建问题,忽略第一个事件:

Private WithEvents fsw As New System.IO.FileSystemWatcher
Private complete As New List(Of String)

Private Sub fsw_Created(ByVal sender As Object, _
    ByVal e As System.IO.FileSystemEventArgs) Handles fsw.Created

    If Not complete.Contains(e.FullPath) Then
        complete.Add(e.FullPath)

    Else
        complete.Remove(e.FullPath)
        Dim th As New Threading.Thread(AddressOf hprocess)
        th.Start(e)

    End If

End Sub

0

我使用了一种更简单的方法。

  1. 布尔值 - 如果正在执行某些操作,则为 true。当结束时,为 false。
  2. 在处理之前,将其添加到 HashSet 中。这样我就不会重复元素。
  3. 每隔 30 分钟,计时器的经过事件会运行,如果没有正在执行的作业,则会清除列表(只需 hashset = new hashset)。

0

代码具有可定制的禁用第二个观察程序引发阻塞时间间隔,并且不会阻止其他观察程序(如果存在)。

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

0

试试这个!

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

}

0

我不得不结合上面帖子中的几个想法并添加文件锁定检查才能使它对我起作用:

FileSystemWatcher fileSystemWatcher;

private void DirectoryWatcher_Start()
{
    FileSystemWatcher fileSystemWatcher = new FileSystemWatcher
    {
        Path = @"c:\mypath",
        NotifyFilter = NotifyFilters.LastWrite,
        Filter = "*.*",
        EnableRaisingEvents = true
    };

    fileSystemWatcher.Changed += new FileSystemEventHandler(DirectoryWatcher_OnChanged);
}

private static void WaitUntilFileIsUnlocked(String fullPath, Action<String> callback, FileAccess fileAccess = FileAccess.Read, Int32 timeoutMS = 10000)
{
    Int32 waitMS = 250;
    Int32 currentMS = 0;
    FileInfo file = new FileInfo(fullPath);
    FileStream stream = null;
    do
    {
        try
        {
            stream = file.Open(FileMode.Open, fileAccess, FileShare.None);
            stream.Close();
            callback(fullPath);
            return;
        }
        catch (IOException)
        {
        }
        finally
        {
            if (stream != null)
                stream.Dispose();
        }
        Thread.Sleep(waitMS);
        currentMS += waitMS;
    } while (currentMS < timeoutMS);
}    

private static Dictionary<String, DateTime> DirectoryWatcher_fileLastWriteTimeCache = new Dictionary<String, DateTime>();

private void DirectoryWatcher_OnChanged(Object source, FileSystemEventArgs ev)
{
    try
    {
        lock (DirectoryWatcher_fileLastWriteTimeCache)
        {
            DateTime lastWriteTime = File.GetLastWriteTime(ev.FullPath);
            if (DirectoryWatcher_fileLastWriteTimeCache.ContainsKey(ev.FullPath))
            {
                if (DirectoryWatcher_fileLastWriteTimeCache[ev.FullPath].AddMilliseconds(500) >= lastWriteTime)
                    return;     // file was already handled
            }

            DirectoryWatcher_fileLastWriteTimeCache[ev.FullPath] = lastWriteTime;
        }

        Task.Run(() => WaitUntilFileIsUnlocked(ev.FullPath, fullPath =>
        {
            // do the job with fullPath...
        }));

    }
    catch (Exception e)
    {
        // handle exception
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接