Programing

FileSystemWatcher 변경된 이벤트가 두 번 발생합니다

lottogame 2020. 3. 10. 08:21
반응형

FileSystemWatcher 변경된 이벤트가 두 번 발생합니다


텍스트 파일을 찾고있는 응용 프로그램이 있으며 파일에 변경 사항이 있으면 OnChangedeventhandler를 사용하여 이벤트를 처리하고 있습니다. 나는 사용하고 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클래스 의 잘 알려진 버그 / 기능이라는 것을 두려워합니다 . 이것은 클래스의 문서에서 가져온 것입니다.

특정 상황에서 단일 작성 이벤트가 구성 요소가 처리하는 여러 개의 작성 이벤트를 생성하는 것을 알 수 있습니다. 예를 들어 FileSystemWatcher 구성 요소를 사용하여 디렉토리에서 새 파일 작성을 모니터 한 다음 메모장을 사용하여 파일을 작성하여 테스트하는 경우 단일 파일 만 작성된 경우에도 두 개의 작성 이벤트가 생성 될 수 있습니다. 메모장은 쓰기 프로세스 중에 여러 파일 시스템 작업을 수행하기 때문입니다. 메모장은 파일 내용을 만든 다음 파일 특성을 만드는 배치로 디스크에 씁니다. 다른 응용 프로그램도 같은 방식으로 수행 될 수 있습니다. FileSystemWatcher는 운영 체제 활동을 모니터하므로 이러한 애플리케이션이 실행하는 모든 이벤트가 선택됩니다.

이제이 텍스트는 Created이벤트 에 관한 것이지만 다른 파일 이벤트에도 동일하게 적용됩니다. 일부 응용 프로그램에서는 NotifyFilter속성 을 사용 하여이 문제를 해결할 수 있지만 내 경험에 따르면 때로는 수동 복제 필터링 (핵)을 수행해야한다고합니다.

얼마 전에 FileFileWatcher에 대한 몇 가지 팁이 있는 페이지를 예약했습니다 . 확인하고 싶을 수도 있습니다.


대리인에서 다음 전략을 사용하여 해당 문제를 "수정했습니다".

// fsw_ is the FileSystemWatcher instance used by my application.

private void OnDirectoryChanged(...)
{
   try
   {
      fsw_.EnableRaisingEvents = false;

      /* do my stuff once asynchronously */
   }

   finally
   {
      fsw_.EnableRaisingEvents = true;
   }
}

해당 파일 타임 스탬프를 확인하여 OnChanged에서 복제 된 모든 이벤트를 FileSystemWatcher감지하고 버릴 수 있습니다 File.GetLastWriteTime. 이렇게 :

DateTime lastRead = DateTime.MinValue;

void OnChanged(object source, FileSystemEventArgs a)
{
    DateTime lastWriteTime = File.GetLastWriteTime(uri);
    if (lastWriteTime != lastRead)
    {
        doStuff();
        lastRead = lastWriteTime;
    }
    // else discard the (duplicated) OnChanged event
}

다음은 이벤트가 두 번 발생하는 것을 막는 데 도움이 된 솔루션입니다.

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

여기서는 NotifyFilter파일 이름과 크기로만 속성을 설정했습니다 .
watcherFileSystemWatcher의 객체입니다. 이것이 도움이되기를 바랍니다.


내 접근 방식은 다음과 같습니다.

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

이것이 파일을 메일의 첨부 파일로 보내는 프로젝트 에서이 문제를 해결하는 데 사용한 솔루션입니다. 타이머 간격이 짧아도 두 번 발생하는 이벤트를 쉽게 피할 수 있지만 초당 1 메시지가 넘는 사서함을 채우는 것보다 약간의 변경 사항이 누락되어 행복했기 때문에 1000은 괜찮습니다. 적어도 여러 파일이 동시에 변경되는 경우에는 정상적으로 작동합니다.

내가 생각한 또 다른 해결책은 목록을 해당 MD5에 대한 사전 매핑 파일로 바꾸는 것이므로 항목을 삭제할 필요는 없지만 값을 업데이트 할 필요가 있기 때문에 임의의 간격을 선택할 필요가 없습니다. 변경되지 않은 경우 물건을 취소하십시오. 파일이 모니터링되고 점점 더 많은 메모리를 사용함에 따라 메모리에서 사전이 증가하는 단점이 있지만 모니터링되는 파일의 양이 FSW의 내부 버퍼에 따라 다르므로 그렇게 중요하지 않을 수도 있습니다. MD5 컴퓨팅 시간이 코드 성능에 어떤 영향을 미치는지 모르겠다.


내 시나리오는 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;
    }
}

주로 파일 쓰기 시간 정보를 저장하는 해시 테이블을 만듭니다. 그런 다음 해시 테이블에 수정 된 파일 경로가 있고 시간 값이 현재 알려진 파일의 변경 사항과 동일하면 이벤트의 복제본임을 알고 무시합니다.


이 코드로 시도하십시오 :

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

나는 이것이 오래된 문제라는 것을 알고 있지만 동일한 문제가 있었고 위의 해결책 중 어느 것도 내가 직면 한 문제에 대한 트릭을 실제로 수행하지 못했습니다. 파일 이름을 LastWriteTime과 매핑하는 사전을 만들었습니다. 따라서 파일이 사전에 없으면 프로세스를 진행하여 마지막으로 수정 한 시간이 언제인지 확인하고 사전에있는 파일과 다른 경우 코드를 실행하십시오.

    Dictionary<string, DateTime> dateTimeDictionary = new Dictionary<string, DateTime>(); 

        private void OnChanged(object source, FileSystemEventArgs e)
            {
                if (!dateTimeDictionary.ContainsKey(e.FullPath) || (dateTimeDictionary.ContainsKey(e.FullPath) && System.IO.File.GetLastWriteTime(e.FullPath) != dateTimeDictionary[e.FullPath]))
                {
                    dateTimeDictionary[e.FullPath] = System.IO.File.GetLastWriteTime(e.FullPath);

                    //your code here
                }
            }

FileSystemWatcher복사가 완료되었을 때만 이벤트를 트리거하도록 확장되는 클래스로 Git 저장소를 만들었 습니다. 마지막으로 실행 된 모든 변경된 이벤트를 버리고 파일을 읽을 수있을 때만 발생합니다.

FileSystemSafeWatcher를 다운로드 하여 프로젝트에 추가하십시오.

그런 다음이를 정상으로 사용 FileSystemWatcher하고 이벤트가 트리거되는시기를 모니터하십시오.

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

하나의 가능한 '해킹'은 예를 들어 Reactive Extensions를 사용하여 이벤트를 조절하는 것입니다.

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

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

watcher.EnableRaisingEvents = true;

이 경우 시스템에서 충분했지만 50ms로 조절하고 있지만 값이 클수록 안전합니다. (그리고 내가 말했듯이 여전히 '해킹'입니다).


여기에 매우 빠르고 간단한 해결 방법이 있으며, 그것은 나를 위해 작동하며, 이벤트가 때때로 한두 번 이상 트리거 될지라도 확인하십시오.

private int fireCount = 0;
private void inputFileWatcher_Changed(object sender, FileSystemEventArgs e)
    {
       fireCount++;
       if (fireCount == 1)
        {
            MessageBox.Show("Fired only once!!");
            dowork();
        }
        else
        {
            fireCount = 0;
        }
    }
}

시도해 볼 수있는 새로운 솔루션이 있습니다. 나를 위해 잘 작동합니다. 변경된 이벤트에 대한 이벤트 핸들러에서 프로그래밍 방식으로 디자이너 출력에서 ​​핸들러를 제거하고 원하는 경우 메시지를 처리기에서 프로그래밍 방식으로 다시 추가하십시오. 예:

public void fileSystemWatcher1_Changed( object sender, System.IO.FileSystemEventArgs e )
    {            
        fileSystemWatcher1.Changed -= new System.IO.FileSystemEventHandler( fileSystemWatcher1_Changed );
        MessageBox.Show( "File has been uploaded to destination", "Success!" );
        fileSystemWatcher1.Changed += new System.IO.FileSystemEventHandler( fileSystemWatcher1_Changed );
    }

주된 이유는 첫 번째 이벤트의 마지막 액세스 시간이 현재 시간 (파일 쓰기 또는 변경된 시간) 이었기 때문입니다. 두 번째 이벤트는 파일의 원래 마지막 액세스 시간이었습니다. 코드로 해결합니다.

        var lastRead = DateTime.MinValue;

        Watcher = new FileSystemWatcher(...)
        {
            NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite,
            Filter = "*.dll",
            IncludeSubdirectories = false,
        };
        Watcher.Changed += (senderObject, ea) =>
        {
            var now = DateTime.Now;
            var lastWriteTime = File.GetLastWriteTime(ea.FullPath);

            if (now == lastWriteTime)
            {
                return;
            }

            if (lastWriteTime != lastRead)
            {
                // do something...
                lastRead = lastWriteTime;
            }
        };

        Watcher.EnableRaisingEvents = true;

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

이 코드는 나를 위해 일했습니다.

        private void OnChanged(object source, FileSystemEventArgs e)
    {

        string fullFilePath = e.FullPath.ToString();
        string fullURL = buildTheUrlFromStudyXML(fullFilePath);

        System.Diagnostics.Process.Start("iexplore", fullURL);

        Timer timer = new Timer();
        ((FileSystemWatcher)source).Changed -= new FileSystemEventHandler(OnChanged);
        timer.Interval = 1000;
        timer.Elapsed += new ElapsedEventHandler(t_Elapsed);
        timer.Start();
    }

    private void t_Elapsed(object sender, ElapsedEventArgs e)
    {
        ((Timer)sender).Stop();
        theWatcher.Changed += new FileSystemEventHandler(OnChanged);
    }

주로 미래를 위해 :)

Rx를 사용하여 래퍼를 작성했습니다.

 public class WatcherWrapper : IDisposable
{
    private readonly FileSystemWatcher _fileWatcher;
    private readonly Subject<FileSystemEventArgs> _infoSubject;
    private Subject<FileSystemEventArgs> _eventSubject;

    public WatcherWrapper(string path, string nameFilter = "*.*", NotifyFilters? notifyFilters = null)
    {
        _fileWatcher = new FileSystemWatcher(path, nameFilter);

        if (notifyFilters != null)
        {
            _fileWatcher.NotifyFilter = notifyFilters.Value;
        }

        _infoSubject = new Subject<FileSystemEventArgs>();
        _eventSubject = new Subject<FileSystemEventArgs>();

        Observable.FromEventPattern<FileSystemEventArgs>(_fileWatcher, "Changed").Select(e => e.EventArgs)
            .Subscribe(_infoSubject.OnNext);
        Observable.FromEventPattern<FileSystemEventArgs>(_fileWatcher, "Created").Select(e => e.EventArgs)
            .Subscribe(_infoSubject.OnNext);
        Observable.FromEventPattern<FileSystemEventArgs>(_fileWatcher, "Deleted").Select(e => e.EventArgs)
            .Subscribe(_infoSubject.OnNext);
        Observable.FromEventPattern<FileSystemEventArgs>(_fileWatcher, "Renamed").Select(e => e.EventArgs)
            .Subscribe(_infoSubject.OnNext);

        // this takes care of double events and still works with changing the name of the same file after a while
        _infoSubject.Buffer(TimeSpan.FromMilliseconds(20))
            .Select(x => x.GroupBy(z => z.FullPath).Select(z => z.LastOrDefault()).Subscribe(
                infos =>
                {
                    if (infos != null)
                        foreach (var info in infos)
                        {
                            {
                                _eventSubject.OnNext(info);
                            }
                        }
                });

        _fileWatcher.EnableRaisingEvents = true;
    }

    public IObservable<FileSystemEventArgs> FileEvents => _eventSubject;


    public void Dispose()
    {
        _fileWatcher?.Dispose();
        _eventSubject.Dispose();
        _infoSubject.Dispose();
    }
}

용법:

var watcher = new WatcherWrapper(_path, "*.info");
// all more complicated and scenario specific filtering of events can be done here    
watcher.FileEvents.Where(x => x.ChangeType != WatcherChangeTypes.Deleted).Subscribe(x => //do stuff)

디렉토리에서 파일을 모니터링하는 방식을 변경했습니다. FileSystemWatcher를 사용하는 대신 다른 스레드의 위치를 ​​폴링 한 다음 파일의 LastWriteTime을 확인합니다.

DateTime lastWriteTime = File.GetLastWriteTime(someFilePath);

이 정보를 사용하고 파일 경로의 색인과 최신 쓰기 시간을 유지하여 변경되었거나 특정 위치에서 작성된 파일을 판별 할 수 있습니다. 이것은 FileSystemWatcher의 이상한 점에서 나를 제거합니다. 주요 단점은 LastWriteTime과 파일에 대한 참조를 저장하기위한 데이터 구조가 필요하지만 신뢰할 수 있고 구현하기 쉽다는 것입니다.


쓰기 위해 파일을 열려고 시도하면 성공하면 다른 응용 프로그램이 파일로 완료되었다고 가정 할 수 있습니다.

private void OnChanged(object source, FileSystemEventArgs e)
{
    try
    {
        using (var fs = File.OpenWrite(e.FullPath))
        {
        }
        //do your stuff
    }
    catch (Exception)
    {
        //no write access, other app not done
    }
}

쓰기 위해 열면 변경된 이벤트가 발생하지 않습니다. 안전해야합니다.


FileReadTime = DateTime.Now;

private void File_Changed(object sender, FileSystemEventArgs e)
{            
    var lastWriteTime = File.GetLastWriteTime(e.FullPath);
    if (lastWriteTime.Subtract(FileReadTime).Ticks > 0)
    {
        // code
        FileReadTime = DateTime.Now;
    }
}

중대한 발굴에 대해 유감스럽게 생각하지만, 나는이 문제를 잠시 동안 싸우고 마침내 여러 개의 해고 된 사건을 처리하는 방법을 생각해 냈습니다. 이 문제를 해결할 때 많은 참고 자료에서 사용했기 때문에이 스레드의 모든 사람들에게 감사드립니다.

여기 내 완전한 코드가 있습니다. 사전을 사용하여 파일을 마지막으로 쓴 날짜와 시간을 추적합니다. 해당 값을 비교하고 같으면 이벤트를 억제합니다. 그런 다음 새 스레드를 시작한 후 값을 설정합니다.

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

요청하지 않으면 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

이것을 시도하십시오, 그것은 잘 작동합니다

  private static readonly FileSystemWatcher Watcher = new FileSystemWatcher();
    static void Main(string[] args)
    {
        Console.WriteLine("Watching....");

        Watcher.Path = @"D:\Temp\Watcher";
        Watcher.Changed += OnChanged;
        Watcher.EnableRaisingEvents = true;
        Console.ReadKey();
    }

    static void OnChanged(object sender, FileSystemEventArgs e)
    {
        try
        {
            Watcher.Changed -= OnChanged;
            Watcher.EnableRaisingEvents = false;
            Console.WriteLine($"File Changed. Name: {e.Name}");
        }
        catch (Exception exception)
        {
            Console.WriteLine(exception);
        }
        finally
        {
            Watcher.Changed += OnChanged;
            Watcher.EnableRaisingEvents = true;
        }
    }

나는 마지막 이벤트에 대해서만 반응하고 싶었다. 경우에 따라서는 리눅스 파일 변경에서도 첫 번째 호출에서 파일이 비어있는 것처럼 보였고 다음에 다시 채워졌고 OS의 경우에 대비하여 시간을 잃어 버리지 않았다. 파일 / 속성 변경을하기로 결정했습니다.

스레딩을 수행하기 위해 .NET 비동기를 사용하고 있습니다.

    private static int _fileSystemWatcherCounts;
    private async void OnChanged(object sender, FileSystemEventArgs e)
    {
        // Filter several calls in short period of time
        Interlocked.Increment(ref _fileSystemWatcherCounts);
        await Task.Delay(100);
        if (Interlocked.Decrement(ref _fileSystemWatcherCounts) == 0)
            DoYourWork();
    }

버퍼 배열에서 중복을 확인하는 기능을 추가 하여이 작업을 수행 할 수있었습니다.

그런 다음 타이머를 사용하여 배열이 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

이 솔루션은 프로덕션 응용 프로그램에서 저에게 효과적이었습니다.

환경:

VB.Net Framework 4.5.2

수동으로 객체 속성 설정 : NotifyFilter = 크기

그런 다음이 코드를 사용하십시오.

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

이 시도!

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

}

위의 게시물에서 여러 아이디어를 결합하고 파일 잠금 검사를 추가하여 나를 위해 일해야했습니다.

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

나는 이와 같은 이중 생성 문제에 접근하여 첫 번째 이벤트를 무시합니다.

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

이 답변들 중 많은 부분이 충격적입니다. 이 문제를 해결하는 XanderUI Control 라이브러리의 코드가 있습니다.

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

다음과 같이 간단하게 중복 검사를 추가합니다.

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

참고 URL : https://stackoverflow.com/questions/1764809/filesystemwatcher-changed-event-is-raised-twice

반응형