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


335

텍스트 파일을 찾고있는 응용 프로그램이 있으며 파일에 변경 사항이 있으면 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하고 저장 하면가 두 번 호출 됩니다.


2
@BrettRigby : 당연합니다. 이러한 잠재적 인 답변 중 어느 것도 문제에 대한 해결책 을 제공하지 않습니다 . 특정 문제에 대한 해결 방법입니다. 사실, 그들 중 아무도 내 특정 문제를 해결하지 못했습니다 (나는 모든 것을 테스트하지는 않았다는 것을 인정해야합니다).

해결 방법이지만 해결 방법의 품질로 판단해야합니다. 변경 사항을 추적하면 완벽하게 작동하며 간단합니다. OP는 중복 이벤트를 억제 할 수있는 방법을 요구하고 있습니다. 이것이 바로 아래 답변입니다. msdn.microsoft.com/ko-kr/library/… 여러 이벤트가 바이러스 백신 또는 기타 "복잡한 파일 시스템"(변명처럼 들릴 수 있음)으로 인해 발생할 수 있다고 설명합니다.
Tyler Montney

2
나는 최근에이 문제 opended github.com/Microsoft/dotnet/issues/347
스테판 Ahlf

2
하나의 이벤트 만받을 수있는 수업을 만들었습니다. github.com/melenaos/FileSystemSafeWatcher
Menelaos Vergis

답변:


276

나는 이것이 FileSystemWatcher클래스 의 잘 알려진 버그 / 기능이라는 것을 두려워합니다 . 이것은 클래스의 문서에서 가져온 것입니다.

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

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

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



151

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

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

14
나는 그것을 시도하고 한 번에 하나의 파일을 수정하면 효과가 있었지만 한 번에 두 개의 파일을 수정하면 (1.txt 및 2.txt 사본 1.txt 사본 및 2.txt 사본) 예상대로 이벤트가 두 개가 아닙니다.
Christopher Painter

2
몇 달이 지났지 만 결국에는 비즈니스 로직을 잠금 문 안에 넣는 메소드를 이벤트 호출이라고 생각합니다. 그렇게하면 여분의 이벤트가 발생하면 차례가 될 때까지 대기하고 이전 반복이 모든 것을 처리 한 이후로 할 일이 없습니다.
Christopher Painter

15
문제가 해결 된 것으로 보이지만 해결되지는 않습니다. 다른 프로세스가 변경 사항을 잃어 버릴 수있는 경우 다른 프로세스의 IO가 비동기이고 처리가 완료 될 때까지 모니터링을 비활성화하여 다른 이벤트와의 경쟁 조건을 생성하기 때문에 작동하지 않는 것처럼 보입니다. 관심의. @ChristopherPainter가 그의 문제를 관찰 한 이유입니다.
Jf Beaulac

14
-1 : 비활성화 된 상태에서 다른 변경 사항이 발생하면 어떻게합니까?
G. Stoynev

2
@cYounes : 당신이하지 않는 물건을 비동기 적으로.
David Brabant

107

해당 파일 의 타임 스탬프를 확인하여 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
}

13
나는 그 솔루션을 좋아하지만 Rx를 사용하여 "올바른"일 "Rename"을하고있다 (관심있는 이벤트의 이름으로 변경 ) :Observable.FromEventPattern<FileSystemEventArgs>(fileSystemWatcher, "Renamed") .Select(e => e.EventArgs) .Distinct(e => e.FullPath) .Subscribe(onNext);
Kjellski

4
뭔가 빠졌습니까? 이것이 어떻게 작동하는지 이해하지 못합니다. 내가 본 것에서 이벤트가 동시에 발생하므로 두 이벤트가 동시에 위의 이벤트에 들어가면 lastRead가 설정되기 전에 모두 실행되기 시작합니다.
Peter Jamsmenson

으로 DateTime만 밀리 초 해상도를 가지고,이 방법을 사용하면 교체 할 경우에도 작동 File.GetLastWriteTime으로 DateTime.Now. 상황에 따라 a.FullName전역 변수에서 in을 사용하여 중복 이벤트를 감지 할 수도 있습니다.
Roland

@PeterJamsmenson 이벤트가 정확히 동시에 발생하지는 않습니다. 예를 들어, 메모장은 디스크에 수정 사항을 저장할 때 여러 이벤트를 생성 할 수 있지만, 이러한 이벤트는 메모장이 저장을 위해 수행해야하는 여러 단계 중에 차례로 순차적으로 발생합니다. Babu의 방법은 훌륭합니다.
Roland

10
발생 된 이벤트가 진드기 분리되어 작동하지 않습니다 : 마지막 쓰기 시간 : 636076274162565607 마지막 쓰기 시간 : 636076274162655722
Asheh

23

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

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

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


9
또한 메모장에서 4 개의 문자 abcd가 포함 된 파일을 만들었습니다. 그런 다음 메모장의 새 인스턴스를 열고 동일한 4자를 입력했습니다. 나는 File | 다른 이름으로 저장하고 동일한 파일을 선택하십시오. 파일이 동일하고 크기와 파일 이름이 변경되지 않습니다. 파일은 같은 네 글자를 갖기 때문에 실행되지 않습니다.
Rhyous

30
파일 크기를 변경하지 않는 진정한 변경이 이루어질 수 있으므로이 기술은 해당 상황에서 실패합니다.
리 그리섬

3
의미있는 변경으로 인해 파일 크기가 수정된다는 것을 알고있는 매우 일반적인 경우라고 생각합니다 (예 : 제 경우는 로그 파일에 추가되었습니다). 이 솔루션을 사용하는 사람은 그 가정을 알고 문서화해야하지만 이것이 바로 내가 필요했던 것입니다.
GrandOpener

1
@GrandOpener : 항상 그런 것은 아닙니다. 제 경우에는 내용이 0 또는 1 인 하나의 문자

8

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

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


해시 테이블을 주기적으로 비우는 것으로 가정합니다.
ThunderGr

이것은 초에 정확하지만 두 변경 사이의 기간이 1 초를 지나기에 충분하면 실패합니다. 또한 더 높은 정확도를 원하면 사용할 수 ToString("o")있지만 더 많은 실패에 대비할 수 있습니다 .
Pragmateek

5
문자열, 사용 DateTime.Equals () 비교하지 마십시오
필립 가미

아뇨. 그들은 평등하지 않습니다. 현재 프로젝트의 경우 약 밀리 초 간격입니다. (newtime-oldtime) .TotalMilliseconds <(임의 임계 값, 일반적으로 5ms)를 사용합니다.
Flynn1179

8

이 코드로 시도하십시오 :

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

1
타이머 대신 세마포어를 사용하는 것이 가장 좋습니다.
Aaron Blenkush

1
무슨 세마포어? 여기 부울 변수 만 있습니다. 또한 주요 문제는 해결되지 않았습니다. FileSystemEventHandler가 여전히 여러 이벤트를 발생시킵니다. 이 코드는 어떤 효과가 있습니까? if (let==false) { ... } else { let = false; }? 이것이 어떻게 투표를했는지 놀랍게도 이것은 StackOverflow 배지의 문제 일뿐입니다.
sɐunıɔ ןɐ qɐp

8

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

// 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 컴퓨팅 시간이 코드 성능에 어떤 영향을 미치는지 모르겠다.


귀하의 솔루션이 저에게 효과적입니다. 파일을 _changedFiles List에 추가하는 것을 잊었습니다. 코드의 첫 부분은 다음과 같아야합니다.lock (_changedFiles) { if (_changedFiles.Contains(e.FullPath)) { return; } _changedFiles.Add(e.FullPath); // add this! } // do your stuff
davidthegrey

나는 위의 4 가지 답변을 하향 투표 하고이 답변을 상향 투표했습니다. 귀하의 답변은 첫 번째 이벤트가 아닌 LAST 이벤트를 수행하여 가장 먼저해야 할 일입니다. @Jorn이 설명했듯이 문제는 파일이 배치로 작성된다는 것입니다. 다른 솔루션은 효과가 없었습니다.
CodingYourLife

귀하의 솔루션은 스레드 안전하지 않습니다. 은 _changedFiles여러 스레드에서 액세스 할 수 있습니다. 이를 해결하는 한 가지 방법은 ConcurrentDictionary대신 을 사용하는 것입니다 List. 또 다른 방법은 전류 FormTimer.SynchronizingObject속성뿐만 아니라 속성 에 할당하는 것 FileSystemWatcher.SynchronizingObject입니다.
Theodor Zoulias

5

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

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

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

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

디렉토리에서 이벤트가 발생하면 실패한 것 같습니다. 파일을 열기 전에 디렉토리 확인을 감아 서 작동시켰다
Sam

예제의 오타에도 불구하고, 이것은 나에게 실용적인 해결책 인 것 같습니다. 그러나 필자의 경우 1 초 내에 수십 가지 업데이트가있을 수 있으므로 변경 사항을 놓치지 않도록 _consolidationInterval을 크게 낮추어야했습니다. 10ms는 괜찮은 것 같지만 _consolidationInterval을 50ms로 설정하면 업데이트의 약 50 %가 여전히 느슨합니다. 여전히 가장 적합한 값을 찾기 위해 몇 가지 테스트를 수행해야합니다.

_consolidationInterval이 저에게 효과적입니다. 누군가 이것을 포크하고 NuGet 패키지로 만들고 싶습니다.
zumalifeguard 5

1
감사합니다 :) 그것은 내 문제를 해결 .. 생성 및 복사 이벤트 가이 문제를 잘 해결하기 위해 하나의 감시자와 제대로 작동하기를 바랍니다. stackoverflow.com/questions/55015132/…
techno

1
이것은 우수하다. 나는 그것을 프로젝트에 구현했으며 그것을 깨려고 시도한 모든 시도를 이겼다. 감사합니다.
Christh

4

나는 이것이 오래된 문제라는 것을 알고 있지만 같은 문제가 있었고 위의 해결책 중 어느 것도 내가 직면 한 문제에 대한 트릭을 실제로 수행하지 못했습니다. 파일 이름을 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
                }
            }

이것은 견고한 솔루션이지만 코드 줄이 없습니다. 에 your code here섹션 추가하거나 dateTimeDictionary을 업데이트해야합니다. dateTimeDictionary[e.FullPath] = System.IO.File.GetLastWriteTime(e.FullPath);
DiamondDrake

나를 위해 일하지 않았다. 내 변경 처리기가 두 번 호출되고 파일에 두 번째 타임 스탬프가 다릅니다. 파일 크기가 커서 쓰기가 처음으로 진행 되었기 때문일 수 있습니다. 중복 이벤트를 축소하는 타이머가 더 효과적이라는 것을 알았습니다.
마이클

3

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

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

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

watcher.EnableRaisingEvents = true;

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


나는 .Distinct(e => e.FullPath)더 직관적 인 방법을 찾았습니다. 그리고 API에서 예상되는 동작이 복원되었습니다.
Kjellski

3

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

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

처음에는 이것이 효과가 있다고 생각했지만 그렇지 않습니다. 파일 내용이 때로는 덮어 쓰여지고 파일이 삭제되고 다시 생성되는 경우가 있습니다. 파일을 덮어 쓸 경우 솔루션이 작동하는 것처럼 보이지만 파일을 다시 생성하는 경우 항상 작동하지는 않습니다. 후자의 경우 때때로 이벤트가 손실됩니다.

다른 유형의 이벤트를 정렬하고 개별적으로 처리하려고 시도하면 가능한 해결 방법을 제공합니다. 행운을 빕니다.
Xiaoyuvax

테스트하지는 않았지만 이것이 생성 및 삭제에 작동하지 않는지 확실하지 않습니다. fireCount ++와 if () 문은 모두 원자 적이므로 기다릴 필요가 없습니다. 서로 경쟁하는 두 개의 트리거 된 이벤트가있는 경우에도 마찬가지입니다. 나는 당신의 문제를 일으키는 다른 것이 있어야한다고 생각합니다. (잃어버린? 당신은 무엇을 의미합니까?)
Xiaoyuvax

3

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

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

1
델리게이트 유형의 생성자를 호출 할 필요는 없습니다. this.fileSystemWatcher1.Changed -= this.fileSystemWatcher1_Changed;옳은 일을해야합니다
bartonjs

@bartonjs 감사합니다. 왜 전체 생성자를 호출했는지 잘 모르겠습니다. 솔직히 그것의 초보자 실수 일 가능성이 높습니다. 그럼에도 불구하고 수정의 해킹이 상당히 잘 작동하는 것처럼 보입니다.
Fancy_Mammoth

2

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

        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;


2

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

2

이 코드는 저에게 효과적이었습니다.

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

2

주로 미래를 위해 :)

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)

1

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

DateTime lastWriteTime = File.GetLastWriteTime(someFilePath);

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


9
시스템 이벤트 알림을받지 않고 백그라운드 사이클을 레코딩해야합니다.
Matthew Whited

1

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

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

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


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

1
이것이 질문에 대한 가장 좋은 해결책 일 수 있지만, 왜이 접근법을 선택했는지와 왜 효과가 있다고 생각하는지에 대한 의견을 추가하는 것이 좋습니다. :)
waka

1

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

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

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

내 프로젝트 중 하나에서 이것을 사용했습니다. 잘 작동합니다!
Tyler Montney

발생 된 이벤트가 진드기처럼 작동하지 않습니다. 마지막 쓰기 시간 : 636076274162565607 마지막 쓰기 시간 : 636076274162655722
프로그래밍 교수,

1

요청하지 않은 경우 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

1

필자의 경우 삽입이 완료되는 즉시 다른 응용 프로그램에 의해 삽입 된 텍스트 파일의 마지막 줄을 가져와야합니다. 여기 내 해결책이 있습니다. 첫 번째 이벤트가 발생하면 감시자가 다른 사람을 키우지 못하게하고 타이머 기능을 호출합니다 .OnChanged 핸들 함수가 호출 될 때 텍스트 파일의 크기가 필요하지만 그 당시의 크기는 실제 크기가 아니기 때문에 삽입 직전에 파일의 크기입니다. 그래서 올바른 파일 크기로 진행하기 위해 잠시 기다립니다.

private FileSystemWatcher watcher = new FileSystemWatcher();
...
watcher.Path = "E:\\data";
watcher.NotifyFilter = NotifyFilters.LastWrite ;
watcher.Filter = "data.txt";
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = true;

...

private void OnChanged(object source, FileSystemEventArgs e)
   {
    System.Timers.Timer t = new System.Timers.Timer();
    try
    {
        watcher.Changed -= new FileSystemEventHandler(OnChanged);
        watcher.EnableRaisingEvents = false;

        t.Interval = 500;
        t.Elapsed += (sender, args) => t_Elapsed(sender, e);
        t.Start();
    }
    catch(Exception ex) {
        ;
    }
}

private void t_Elapsed(object sender, FileSystemEventArgs e) 
   {
    ((System.Timers.Timer)sender).Stop();
       //.. Do you stuff HERE ..
     watcher.Changed += new FileSystemEventHandler(OnChanged);
     watcher.EnableRaisingEvents = true;
}

1

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

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

1

나는 마지막 이벤트에 대해서만 반응하고 싶었다. 경우에 따라서는 리눅스 파일 변경에서도 첫 번째 호출에서 파일이 비어있는 것처럼 보였고 다음에 다시 채워졌고 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();
    }

1

이 문제를 해결하는 가장 좋은 해결책은 반응 확장을 사용하는 것입니다. 이벤트를 관찰 가능으로 변환 할 때 Throttling (..) (원래 Debounce (..))을 추가하면됩니다.

여기에 샘플 코드

        var templatesWatcher = new FileSystemWatcher(settingsSnapshot.Value.TemplatesDirectory)
        {
            NotifyFilter = NotifyFilters.LastWrite,
            IncludeSubdirectories = true
        };

        templatesWatcher.EnableRaisingEvents = true;

        Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
                addHandler => templatesWatcher.Changed += addHandler,
                removeHandler => templatesWatcher.Changed -= removeHandler)
            .Throttle(TimeSpan.FromSeconds(5))
            .Subscribe(args =>
            {
                _logger.LogInformation($"Template file {args.EventArgs.Name} has changed");
                //TODO do something
            });

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 = 크기

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

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

이 시도!

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

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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.