Sự kiện thay đổi FileSystemWatcher được nâng lên hai lần


334

Tôi có một ứng dụng mà tôi đang tìm kiếm một tệp văn bản và nếu có bất kỳ thay đổi nào được thực hiện đối với tệp tôi đang sử dụng OnChangedeventhandler để xử lý sự kiện. Tôi đang sử dụng NotifyFilters.LastWriteTimenhưng sự kiện vẫn bị đuổi việc hai lần. Đây là mã.

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

Trong trường hợp của tôi, OnChangednó được gọi hai lần, khi tôi thay đổi tệp văn bản version.txtvà lưu nó.


2
@BrettRigby: Không có gì lạ. Không có câu trả lời tiềm năng nào cung cấp giải pháp cho vấn đề. Họ là tất cả các giải pháp cho các vấn đề cụ thể. Trên thực tế, không ai trong số họ giải quyết vấn đề cụ thể của tôi (tôi phải thừa nhận, tôi chưa kiểm tra tất cả trong số họ).

Đó là một cách giải quyết, nhưng nó cần được đánh giá bằng chất lượng của cách giải quyết. Theo dõi các thay đổi hoạt động hoàn hảo, và nó đơn giản. OP đang yêu cầu một cách để ngăn chặn các sự kiện trùng lặp và đó là những gì các câu trả lời dưới đây đưa ra. msdn.microsoft.com/en-us/l Library / Từ Giải thích rằng nhiều sự kiện có thể do chống vi-rút hoặc "nội dung hệ thống tệp phức tạp" khác (nghe có vẻ như là một cái cớ).
Tyler Montney

2
Gần đây tôi đã phản đối vấn đề này github.com/Microsoft/dotnet/issues/347
Stephan Ahlf

2
Tôi đã tạo một lớp giúp bạn chỉ có một sự kiện. Bạn có thể nhận mã từ github.com/melenaos/FileSystemSafeWatcher
Menelaos Vergis

Câu trả lời:


275

Tôi sợ rằng đây là một lỗi / tính năng nổi tiếng của FileSystemWatcherlớp. Đây là từ tài liệu của lớp:

Bạn có thể nhận thấy trong một số trường hợp nhất định rằng một sự kiện tạo duy nhất tạo ra nhiều sự kiện đã tạo được xử lý bởi thành phần của bạn. Ví dụ: nếu bạn sử dụng thành phần FileSystemWatcher để theo dõi việc tạo tệp mới trong thư mục, sau đó kiểm tra nó bằng cách sử dụng Notepad để tạo tệp, bạn có thể thấy hai sự kiện đã tạo được tạo ngay cả khi chỉ tạo một tệp. Điều này là do Notepad thực hiện nhiều hành động hệ thống tệp trong quá trình viết. Notepad ghi vào đĩa theo từng đợt tạo nội dung của tệp và sau đó là thuộc tính tệp. Các ứng dụng khác có thể thực hiện theo cách tương tự. Vì FileSystemWatcher giám sát các hoạt động của hệ điều hành, tất cả các sự kiện mà các ứng dụng này kích hoạt sẽ được chọn.

Bây giờ bit văn bản này là về Createdsự kiện, nhưng điều tương tự cũng áp dụng cho các sự kiện tập tin khác. Trong một số ứng dụng, bạn có thể khắc phục điều này bằng cách sử dụng thuộc NotifyFiltertính, nhưng kinh nghiệm của tôi nói rằng đôi khi bạn cũng phải thực hiện một số bộ lọc trùng lặp thủ công (hack).

Cách đây một thời gian, tôi đã đánh dấu một trang với một vài mẹo FileSystemWatcher . Bạn có thể muốn kiểm tra nó.



150

Tôi đã "khắc phục" vấn đề đó bằng cách sử dụng chiến lược sau trong đại biểu của mình:

// 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
Tôi đã thử nó và nó hoạt động nếu tôi sửa đổi một tệp cùng một lúc nhưng nếu tôi sửa đổi hai tệp cùng một lúc (như bản sao 1.txt và 2.txt thành bản sao của 1.txt và bản sao của 2.txt) thì nó chỉ tăng một sự kiện không phải hai như mong đợi.
Christopher Họa sĩ

2
Đã được một vài tháng nhưng tôi nghĩ rằng những gì tôi đã làm là tổ chức sự kiện gọi một phương thức đưa logic kinh doanh vào trong một tuyên bố khóa. Theo cách đó, nếu tôi nhận được các sự kiện bổ sung, họ sẽ xếp hàng cho đến khi đến lượt của họ và họ không phải làm gì vì lần lặp trước đó đã lo tất cả mọi thứ.
Christopher Họa sĩ

15
Điều này xuất hiện để khắc phục vấn đề, nhưng nó không. Nếu một quá trình khác đang thực hiện các thay đổi mà bạn có thể mất chúng, thì lý do nó xuất hiện là do IO của quá trình khác không đồng bộ và bạn vô hiệu hóa giám sát cho đến khi bạn xử lý xong, do đó tạo ra một điều kiện chạy đua với các sự kiện khác có thể là lãi. Đó là lý do tại sao @ChristopherPainter quan sát vấn đề của mình.
Jf Beaulac

14
-1: Điều gì xảy ra nếu một thay đổi khác mà bạn quan tâm xảy ra trong khi bị vô hiệu hóa?
G. Stoynev

2
@cYounes: trừ khi bạn làm công cụ của mình không đồng bộ.
David Brabant

107

Bất kỳ OnChangedsự kiện trùng lặp nào từ FileSystemWatchercó thể được phát hiện và loại bỏ bằng cách kiểm tra File.GetLastWriteTimedấu thời gian trên tệp đang đề cập. Thích như vậy:

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
Tôi thích giải pháp đó, nhưng tôi đã sử dụng Rx để làm điều "đúng" (đổi "Rename"tên của sự kiện mà bạn quan tâm):Observable.FromEventPattern<FileSystemEventArgs>(fileSystemWatcher, "Renamed") .Select(e => e.EventArgs) .Distinct(e => e.FullPath) .Subscribe(onNext);
Kjellski

4
Tui bỏ lỡ điều gì vậy? Tôi không hiểu làm thế nào điều này sẽ làm việc. Từ những gì tôi đã thấy các sự kiện diễn ra đồng thời, vì vậy nếu cả hai cùng tham gia vào sự kiện trên cùng một lúc thì cả hai sẽ bắt đầu chạy trước khi LastRead được thiết lập.
Peter Jamsmenson

DateTimechỉ có độ phân giải mili giây, phương pháp này hoạt động ngay cả khi bạn thay thế File.GetLastWriteTimebằng DateTime.Now. Tùy thuộc vào tình huống của bạn, bạn cũng có thể sử dụng a.FullNamebiến toàn cục để phát hiện các sự kiện trùng lặp.
Roland

@PeterJamsmenson Các sự kiện không chính xác diễn ra đồng thời. Ví dụ: Notepad có thể tạo ra một số sự kiện khi lưu sửa đổi vào đĩa, nhưng các sự kiện này được thực hiện tuần tự, lần lượt từng sự kiện, trong một số bước mà Notepad cần thực hiện để lưu. Phương pháp của Babu hoạt động rất tốt.
Roland

10
Không hoạt động khi các sự kiện được phát ra cách nhau: Thời gian viết lần cuối: 636076274162565607 Thời gian viết lần cuối: 636076274162655722
Asheh

23

Đây là giải pháp giúp tôi ngăn chặn sự kiện được nêu ra hai lần:

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

Ở đây tôi đã đặt thuộc NotifyFiltertính chỉ với Tên tệp và kích thước.
watcherlà đối tượng của FileSystemWatcher. Hy vọng điều này sẽ giúp.


9
Ngoài ra, trong Notepad, tôi đã tạo một tệp có bốn ký tự: abcd trong đó. Sau đó tôi đã mở một phiên bản mới của Notepad và nhập cùng bốn ký tự. Tôi đã chọn Tệp | Lưu dưới dạng và chọn cùng một tệp. Tệp giống hệt nhau và kích thước và tên tệp không thay đổi, vì tệp có cùng bốn chữ cái, vì vậy điều này không kích hoạt.
Rhyous

30
Có thể là một thay đổi thực sự có thể được thực hiện mà không làm thay đổi kích thước của tệp, do đó kỹ thuật này sẽ thất bại trong tình huống đó.
Lee Grissom

3
Tôi đoán đó là một trường hợp khá phổ biến khi bạn biết rằng bất kỳ thay đổi có ý nghĩa nào cũng sẽ sửa đổi kích thước tệp (ví dụ: trường hợp của tôi đang nối vào tệp nhật ký). Mặc dù bất cứ ai sử dụng giải pháp này đều nên biết (và tài liệu) giả định đó, đây chính xác là những gì tôi cần.
GrandOpener

1
@GrandOpener: Điều này không phải lúc nào cũng đúng. Trong trường hợp của tôi, tôi đang xem các tệp trong đó nội dung của nó chỉ bao gồm một ký tự là 0 hoặc 1.

8

Kịch bản của tôi là tôi có một máy ảo có máy chủ Linux. Tôi đang phát triển các tập tin trên máy chủ Windows. Khi tôi thay đổi một cái gì đó trong một thư mục trên máy chủ, tôi muốn tất cả các thay đổi sẽ được tải lên, được đồng bộ hóa trên máy chủ ảo thông qua Ftp. Đây là cách tôi loại bỏ sự kiện thay đổi trùng lặp khi tôi ghi vào một tệp (cũng đánh dấu thư mục chứa tệp sẽ được sửa đổi):

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

Chủ yếu tôi tạo một hashtable để lưu trữ thông tin thời gian ghi tập tin. Sau đó, nếu hashtable có filepath được sửa đổi và giá trị thời gian của nó giống với thay đổi của tệp được thông báo hiện tại thì tôi biết đó là bản sao của sự kiện và bỏ qua nó.


Tôi giả sử bạn làm trống hashtable định kỳ.
ThunderGr

Điều này sẽ chính xác đến lần thứ hai nhưng nếu khoảng thời gian giữa hai thay đổi đủ dài để vượt qua một giây thì nó sẽ thất bại. Hơn nữa, nếu bạn muốn độ chính xác cao hơn, bạn có thể sử dụng ToString("o")nhưng hãy chuẩn bị cho nhiều thất bại hơn.
Pragmateek

5
Đừng so sánh các chuỗi, sử dụng DateTime.Equals ()
Phillip Kamikaze

Không, không. Họ không bằng nhau. Trong trường hợp dự án hiện tại của tôi, chúng cách nhau một phần nghìn giây. Tôi sử dụng (newtime-oldtime) .TotalMilliseconds <(ngưỡng tùy ý, thường là 5ms).
Flynn1179

8

Hãy thử với mã này:

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
Đây là giải pháp tốt nhất, sử dụng semaphore thay vì hẹn giờ.
Aaron Blenkush

1
Semaphore là gì? Tôi chỉ thấy một biến boolean ở đây. Hơn nữa, vấn đề chính không được giải quyết: FileSystemEventHandler vẫn đang bắn nhiều sự kiện. Và những gì hiệu quả có mã này? if (let==false) { ... } else { let = false; }? Không thể tin được làm thế nào điều này có được upvote, đây chỉ là vấn đề của huy hiệu StackOverflow.
sɐunıɔ qɐp

8

Đây là cách tiếp cận của tôi:

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

Đây là giải pháp tôi đã sử dụng để giải quyết vấn đề này trong một dự án nơi tôi đang gửi tệp dưới dạng tệp đính kèm trong thư. Nó sẽ dễ dàng tránh được sự kiện bị bắn hai lần ngay cả với khoảng thời gian hẹn giờ nhỏ hơn nhưng trong trường hợp của tôi thì 1000 vẫn ổn vì tôi thấy vui hơn khi bỏ lỡ vài thay đổi so với việc làm ngập hộp thư với> 1 tin nhắn mỗi giây. Ít nhất là nó hoạt động tốt trong trường hợp một số tệp được thay đổi cùng một lúc.

Một giải pháp khác mà tôi nghĩ sẽ là thay thế danh sách bằng các tệp ánh xạ từ điển thành MD5 tương ứng của chúng, vì vậy bạn sẽ không phải chọn một khoảng tùy ý vì bạn sẽ không phải xóa mục nhập mà cập nhật giá trị của nó và hủy bỏ công cụ của bạn nếu nó không thay đổi. Nó có nhược điểm là có một Từ điển phát triển trong bộ nhớ vì các tệp được theo dõi và ăn ngày càng nhiều bộ nhớ, nhưng tôi đã đọc được ở đâu đó rằng lượng tệp được theo dõi phụ thuộc vào bộ đệm bên trong của FSW, vì vậy có lẽ không quá quan trọng. Không biết thời gian tính toán MD5 sẽ ảnh hưởng đến hiệu suất mã của bạn như thế nào, cẩn thận = \


Giải pháp của bạn làm việc tuyệt vời cho tôi. Chỉ, bạn đã quên thêm tệp vào Danh sách _changedFiles. Phần đầu tiên của mã sẽ giống như thế này:lock (_changedFiles) { if (_changedFiles.Contains(e.FullPath)) { return; } _changedFiles.Add(e.FullPath); // add this! } // do your stuff
davidthegrey

Tôi đã đánh giá thấp 4 câu trả lời ở trên và đánh giá cao câu trả lời này. Câu trả lời của bạn là câu đầu tiên làm những gì cần làm bằng cách tham gia sự kiện LAST chứ không phải câu đầu tiên. Theo giải thích của @Jorn, vấn đề là các tệp được ghi theo lô. Các giải pháp khác không hiệu quả với tôi.
CodingYourLife

Giải pháp của bạn không phải là chủ đề an toàn. Các _changedFilesđược truy cập từ nhiều chủ đề. Một cách để khắc phục nó là sử dụng ConcurrentDictionarythay thế List. Một cách khác là gán dòng điện Formcho Timer.SynchronizingObjecttài sản, cũng như cho FileSystemWatcher.SynchronizingObjecttài sản.
Theodor Zoulias

5

Tôi đã tạo một repo Git với một lớp mở rộng FileSystemWatcherđể kích hoạt các sự kiện chỉ khi sao chép xong. Nó loại bỏ tất cả các sự kiện đã thay đổi ngoại trừ lần cuối cùng và nó chỉ nâng lên khi tệp có sẵn để đọc.

Tải xuống FileSystemSafeWatcher và thêm nó vào dự án của bạn.

Sau đó sử dụng nó như một bình thường FileSystemWatchervà theo dõi khi các sự kiện được kích hoạt.

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

Điều này dường như thất bại khi một sự kiện được đưa ra trên một thư mục. Tôi đã làm cho nó hoạt động bằng cách gói một kiểm tra thư mục trước khi mở tệp
Sam

Mặc dù lỗi chính tả trong ví dụ, đây dường như là một giải pháp khả thi cho tôi. Tuy nhiên, trong trường hợp của tôi có thể có hàng tá cập nhật trong vòng một giây, vì vậy tôi phải hạ _consolidationInterval một cách quyết liệt để không bỏ lỡ bất kỳ thay đổi nào. Mặc dù 10 ms có vẻ ổn nhưng tôi vẫn mất khoảng 50% các bản cập nhật nếu tôi đặt _consolidationInterval thành 50 ms. Tôi vẫn phải chạy một số thử nghiệm để tìm giá trị phù hợp nhất.

_consolidationInterval dường như hoạt động tốt cho tôi. Tôi muốn ai đó dùng cái nĩa này và biến nó thành gói NuGet.
bảo vệ zumalififard

1
Cảm ơn :) Nó đã giải quyết vấn đề của tôi .. Hy vọng các sự kiện được tạo và sao chép sẽ hoạt động đúng với một người theo dõi duy nhất để giải quyết vấn đề này tốt. stackoverflow.com/questions/55015132/ từ
Techno

1
Thật tuyệt vời. Tôi đã thực hiện nó trong dự án của mình và nó đã đánh bại mọi nỗ lực tôi đã cố gắng để phá vỡ nó. Cảm ơn bạn.
Christh

4

Tôi biết đây là một vấn đề cũ, nhưng có cùng một vấn đề và không có giải pháp nào ở trên thực sự làm được mẹo cho vấn đề mà tôi đang gặp phải. Tôi đã tạo một từ điển ánh xạ tên tệp với LastWriteTime. Vì vậy, nếu tệp không có trong từ điển sẽ tiếp tục với quy trình kiểm tra khôn ngoan khác để xem thời điểm sửa đổi lần cuối là khi nào và nếu khác với những gì trong từ điển thì hãy chạy mã.

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

Đây là một giải pháp vững chắc, nhưng nó thiếu một dòng mã. trong your code herephần này, bạn nên thêm hoặc cập nhật dateTimeDipedia. dateTimeDictionary[e.FullPath] = System.IO.File.GetLastWriteTime(e.FullPath);
DiamondDrake

Không làm việc cho tôi. Trình xử lý thay đổi của tôi được gọi hai lần và tệp có dấu thời gian khác lần thứ hai. Có thể bởi vì nó là một tệp lớn và lần đầu tiên ghi được tiến hành. Tôi tìm thấy một bộ đếm thời gian để thu gọn các sự kiện trùng lặp hoạt động tốt hơn.
michael

3

Một 'hack' có thể sẽ là điều tiết các sự kiện bằng cách sử dụng Tiện ích mở rộng phản ứng chẳng hạn:

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

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

watcher.EnableRaisingEvents = true;

Trong trường hợp này, tôi điều chỉnh đến 50ms, trên hệ thống của tôi là đủ, nhưng giá trị cao hơn sẽ an toàn hơn. (Và như tôi đã nói, nó vẫn là một 'hack').


Tôi đã sử dụng .Distinct(e => e.FullPath)mà tôi tìm thấy cách trực quan hơn để đối phó. Và bạn đã khôi phục hành vi sẽ được mong đợi từ API.
Kjellski

3

Tôi có một cách giải quyết rất nhanh chóng và đơn giản ở đây, nó hoạt động với tôi và bất kể sự kiện nào sẽ được kích hoạt một hoặc hai lần hoặc nhiều lần, hãy kiểm tra xem:

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

Lúc đầu, tôi nghĩ rằng nó sẽ làm việc cho tôi, nhưng nó không làm được. Tôi có một tình huống, trong đó nội dung tệp đôi khi chỉ bị ghi đè và lần khác tệp bị xóa và được tạo lại. Mặc dù giải pháp của bạn dường như hoạt động trong trường hợp tệp bị ghi đè, nhưng nó không luôn hoạt động trong trường hợp tệp được tạo lại. Trong trường hợp sau sự kiện đôi khi bị mất.

Cố gắng sắp xếp các loại sự kiện khác nhau và giải quyết chúng một cách riêng biệt, tôi chỉ đưa ra một cách giải quyết có thể. chúc may mắn.
Xiaoyuvax

mặc dù không kiểm tra nó, tôi không chắc chắn điều này không hoạt động để tạo và xóa. về mặt lý thuyết cũng nên được áp dụng. Giả sử câu lệnh fireCount ++ và if () đều là nguyên tử và sẽ không được chờ đợi. thậm chí với hai sự kiện kích hoạt cạnh tranh với nhau. Tôi đoán phải có một cái gì đó khác gây ra rắc rối của bạn. (bị mất? Ý bạn là gì?)
Xiaoyuvax

3

Đây là một giải pháp mới bạn có thể thử. Hoạt động tốt cho tôi. Trong trình xử lý sự kiện cho sự kiện đã thay đổi, lập trình loại bỏ trình xử lý khỏi trình thiết kế xuất ra một thông báo nếu muốn, sau đó lập trình thêm trình xử lý trở lại. thí dụ:

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
Bạn không cần phải gọi hàm tạo của đại biểu. this.fileSystemWatcher1.Changed -= this.fileSystemWatcher1_Changed;nên làm điều đúng đắn
bartonjs

@bartonjs Cảm ơn vì điều đó. Tôi không chắc tại sao tôi gọi toàn bộ nhà xây dựng. Thành thật mà nói nó rất có thể là một lỗi người mới. Bất kể mặc dù có vẻ như bản sửa lỗi của tôi hoạt động khá tốt.
Fancy_Mammoth

2

Lý do chính là thời gian truy cập cuối cùng của sự kiện đầu tiên là thời gian hiện tại (ghi tập tin hoặc thời gian thay đổi). sau đó sự kiện thứ hai là lần truy cập cuối cùng của tập tin. Tôi giải quyết theo mã.

        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

Tôi đã dành một lượng thời gian đáng kể để sử dụng FileSystemWatcher và một số cách tiếp cận ở đây sẽ không hiệu quả. Tôi thực sự thích cách tiếp cận vô hiệu hóa các sự kiện, nhưng thật không may, nó không hoạt động nếu có> 1 tệp bị bỏ, tệp thứ hai sẽ bị bỏ qua nhiều nhất nếu không phải tất cả các lần. Vì vậy, tôi sử dụng cách tiếp cận sau:

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

Mã này làm việc cho tôi.

        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

chủ yếu là cho tương lai tôi :)

Tôi đã viết một trình bao bọc bằng 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();
    }
}

Sử dụng:

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

Tôi đã thay đổi cách tôi theo dõi các tập tin trong thư mục. Thay vì sử dụng FileSystemWatcher, tôi thăm dò các vị trí trên một luồng khác và sau đó xem LastWriteTime của tệp.

DateTime lastWriteTime = File.GetLastWriteTime(someFilePath);

Sử dụng thông tin này và giữ một chỉ mục của đường dẫn tệp và thời gian ghi mới nhất tôi có thể xác định các tệp đã thay đổi hoặc đã được tạo ở một vị trí cụ thể. Điều này loại bỏ tôi khỏi những điều kỳ lạ của FileSystemWatcher. Nhược điểm chính là bạn cần một cấu trúc dữ liệu để lưu trữ LastWriteTime và tham chiếu đến tệp, nhưng nó đáng tin cậy và dễ thực hiện.


9
cũng như bạn phải ghi chu kỳ nền thay vì được thông báo bởi một sự kiện hệ thống.
Matthew Whited

1

Bạn có thể thử mở nó để ghi và nếu thành công thì bạn có thể giả sử ứng dụng khác được thực hiện với tệp.

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

Chỉ cần mở nó để viết dường như không làm tăng sự kiện đã thay đổi. Vì vậy, nó nên được an toàn.


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
Mặc dù đây có thể là giải pháp tốt nhất cho câu hỏi được hỏi, nhưng thật tuyệt khi thêm một số nhận xét về lý do tại sao bạn chọn phương pháp này và lý do tại sao bạn nghĩ rằng nó hiệu quả. :)
waka

1

Xin lỗi vì đã đào mộ, nhưng tôi đã chiến đấu với vấn đề này một thời gian và cuối cùng đã tìm ra cách để xử lý nhiều sự kiện bị sa thải này. Tôi muốn cảm ơn tất cả mọi người trong chủ đề này vì tôi đã sử dụng nó trong nhiều tài liệu tham khảo khi chiến đấu với vấn đề này.

Đây là mã hoàn chỉnh của tôi. Nó sử dụng một từ điển để theo dõi ngày và thời gian ghi lần cuối của tập tin. Nó so sánh giá trị đó, và nếu nó giống nhau, nó triệt tiêu các sự kiện. Sau đó, nó đặt giá trị sau khi bắt đầu chuỗi mới.

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

Đã sử dụng điều này trong một trong các dự án của tôi. Hoạt động tuyệt vời!
Tyler Montney

Không hoạt động khi các sự kiện được phát ra cách nhau: Thời gian viết cuối cùng: 636076274162565607 Thời gian viết cuối cùng: 636076274162655722
Giáo sư lập trình

1

Sự kiện nếu không được hỏi, thật xấu hổ vì không có mẫu giải pháp sẵn sàng cho F #. Để khắc phục điều này ở đây là công thức của tôi, chỉ vì tôi có thể và F # là một ngôn ngữ .NET tuyệt vời.

Các sự kiện trùng lặp được lọc ra bằng FSharp.Control.Reactivegói, đây chỉ là một trình bao bọc F # cho các tiện ích mở rộng phản ứng. Tất cả những gì có thể được nhắm mục tiêu đến khung đầy đủ hoặc 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

Trong trường hợp của tôi cần lấy dòng cuối cùng của tệp văn bản được chèn bởi ứng dụng khác, ngay sau khi chèn xong. Đây là giải pháp của tôi. Khi sự kiện đầu tiên được đưa ra, tôi vô hiệu hóa trình theo dõi để tăng người khác, sau đó tôi gọi bộ đếm thời gian TimeElapsedEvent vì khi chức năng xử lý của tôi OnChanged được gọi là tôi cần kích thước của tệp văn bản, nhưng kích thước tại thời điểm đó không phải là kích thước thực tế, nó là kích thước của tập tin trước khi chèn. Vì vậy, tôi chờ một lúc để tiến hành với kích thước tập tin phù hợp.

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

Hãy thử nó, nó hoạt động tốt

  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

Tôi chỉ muốn phản ứng ở sự kiện cuối cùng, chỉ trong trường hợp, cũng là khi thay đổi tệp linux, có vẻ như tệp đó trống trong cuộc gọi đầu tiên và sau đó điền lại vào lần tiếp theo và không ngại mất một thời gian chỉ trong trường hợp HĐH quyết định thực hiện một số thay đổi tập tin / thuộc tính.

Tôi đang sử dụng .NET async ở đây để giúp tôi thực hiện phân luồng.

    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

Tôi nghĩ giải pháp tốt nhất để giải quyết vấn đề là sử dụng các tiện ích mở rộng phản ứng Khi bạn chuyển đổi sự kiện thành có thể quan sát được, thì bạn chỉ cần thêm Throttling (..) (ban đầu được gọi là Debounce (..))

Mã mẫu tại đây

        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

Tôi đã có thể làm điều này bằng cách thêm một chức năng kiểm tra các bản sao trong một mảng đệm.

Sau đó thực hiện hành động sau khi mảng không được sửa đổi trong thời gian X bằng cách sử dụng bộ hẹn giờ: - Đặt lại bộ đếm thời gian mỗi khi có gì đó được ghi vào bộ đệm - Thực hiện hành động khi đánh dấu

Điều này cũng bắt một loại trùng lặp khác. Nếu bạn sửa đổi một tệp trong một thư mục, thư mục đó cũng sẽ ném một sự kiện Thay đổi.

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

Giải pháp này đã làm việc cho tôi trên ứng dụng sản xuất:

Môi trường:

Khung VB.Net 4.5.2

Đặt thuộc tính đối tượng thủ công: NotifyFilter = Size

Sau đó sử dụng mã này:

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

Nó sử dụng khái niệm tương tự như @Jamie Krcmar nhưng đối với VB.NET
wpcoder

0

Thử cái này!

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

Tôi đã phải kết hợp một số ý tưởng từ các bài đăng ở trên và thêm kiểm tra khóa tệp để làm cho nó hoạt động với tôi:

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

Tôi đã tiếp cận vấn đề tạo kép như thế này, mà bỏ qua sự kiện đầu tiên:

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.