2013-03-20 20 views
5

cách tránh lỗi của FileSystemWatcher trong C#?Tránh Lỗi quá nhiều thay đổi cùng một lúc trong thư mục

quá nhiều thay đổi cùng một lúc trong thư mục

tôi phải phát hiện tất cả những thay đổi trên một mạng chia sẻ. InternalBufferSize được tăng lên 8192 * 128

+1

Tôi đã chỉnh sửa tiêu đề của bạn. Vui lòng xem, "[Câu hỏi có nên bao gồm" thẻ "trong tiêu đề của họ không?] (Http://meta.stackexchange.com/questions/19190/)", trong đó sự đồng thuận là "không, họ không nên". –

Trả lời

2

Tôi có kinh nghiệm rằng FileSystemWatcher không phải lúc nào cũng là điều đáng tin cậy nhất để sử dụng. Bạn có thể chỉ định bộ lọc để thu hẹp các tệp bạn đang xem (NotifyFilter) hoặc tăng kích thước bộ đệm.

Nhưng tùy theo yêu cầu của bạn, bạn cũng có thể muốn thực hiện theo cách khác, chẳng hạn như bỏ phiếu mỗi x giây để nhận danh sách tệp. Nhưng sau đó bạn có thể cần phải cho chúng tôi biết thêm về trường hợp kinh doanh của bạn.

+0

Lần thử thứ hai cũng bị treo: đối với mỗi NotifyFilter đó là FSW của riêng nó. Tôi phải phát hiện tất cả các thay đổi để thực hiện những thay đổi này thành cơ sở dữ liệu (đường dẫn và tổng kiểm tra). Bỏ phiếu trên một mạng chia sẻ 2TB là không đủ - và có, tôi đã sử dụng lệnh shell để truy cập tất cả các đường dẫn (nhanh hơn nhiều so với System.IO). – DerAbt

+0

Xem thêm http: // stackoverflow.com/questions/239988/filesystemwatcher-vs-polling-to-watch-cho-file-thay đổi mà nói rằng khi sử dụng này trên một phần, nó không phải là đáng tin cậy và bỏ phiếu sẽ là một lựa chọn tốt hơn. –

+0

Bỏ phiếu trên 2 TB không có tùy chọn ;-) Cảm ơn. – DerAbt

2

Từ MSDN;

Hệ điều hành Windows thông báo cho thành phần thay đổi tệp của bạn trong bộ đệm được tạo bởi FileSystemWatcher. Nếu có nhiều thay đổi trong một thời gian ngắn, bộ đệm có thể tràn. Điều này làm cho thành phần mất theo dõi các thay đổi trong thư mục và chỉ cung cấp thông báo về chăn. Tăng kích thước của bộ đệm với thuộc tính InternalBufferSize là đắt, vì nó đến từ bộ nhớ không được phân trang không thể hoán đổi ra đĩa, do đó giữ bộ đệm nhỏ nhưng đủ lớn để không bỏ lỡ bất kỳ sự kiện thay đổi tệp nào. Để tránh tràn bộ đệm, hãy sử dụng các thuộc tính NotifyFilterIncludeSubdirectories để bạn có thể lọc ra các thay đổi không mong muốn thông báo.

+0

Tôi biết. Tôi tạo ra 40 tập tin của Copy'n'Paste và lỗi đã được ném. Chỉ 40 tệp. – DerAbt

+0

@DerAbt _Hmm_, có vẻ như không quá nhiều. Bạn có chắc là bạn không làm gì khác không? –

+0

Có. Tạo một tập tin bằng "New textfile" và sao chép 40 lần vào cùng một thư mục. – DerAbt

0

SHChangeNotifyĐăng ký có thể được sử dụng để nhận thông báo về vỏ.

0

Nó phải được cố định nếu bạn tăng kích thước bộ đệm nhưng không phải là giải pháp thực tế. Bởi vì để đảm bảo nó luôn ghi lại mọi thứ bạn cần để tạo bộ đệm lớn. Và điều đó sẽ ảnh hưởng đến hiệu suất tuyệt vời. Và tôi nghĩ rằng vấn đề hiệu suất có thể được khắc phục bằng cách triển khai đa luồng.

4

Tôi nghĩ rằng tôi có thể đã tìm thấy một mẫu có thể giúp cải thiện việc sử dụng bộ đệm một cách đáng kể.

Vấn đề với lớp này là cho đến khi đại biểu cho các sự kiện kết thúc chạy, nó không thể giải phóng bộ nhớ mà nó sử dụng để giữ thông tin đó.

Đối với cuộc sống của tôi, tôi không biết tại sao InternalBufferSize tối đa được đặt thành 64Kb, nhưng với ý tưởng này, bạn sẽ sử dụng bộ đệm nhỏ hiệu quả hơn nhiều.

Nếu thay vì thực hiện các thao tác bên trong đại biểu, bạn chỉ cần xếp hàng và trì hoãn việc thực thi của họ cho nhân viên nền, dung lượng bộ nhớ được sử dụng sẽ nhỏ hơn đáng kể.

Thực tế, tôi không biết tại sao lớp học lại không thực hiện điều gì đó như thế này ngay từ đầu.

Đoạn mã dưới đây chỉ là mẫu mã cho ý tưởng và không được sử dụng trong môi trường sản xuất, nhưng nó tăng số lượng tệp tôi có thể sao chép và theo dõi đáng kể.

using System; 
using System.Collections.Concurrent; 
using System.ComponentModel; 
using System.IO; 
using System.Threading; 
using NUnit.Framework; 

namespace Soundnet.Synchronisation.FileSystemWatcherTests 
{ 


    /// <summary> 
    /// 
    /// </summary> 
    [TestFixture] 
    public class Tests 
    { 

    static readonly ConcurrentQueue<Change> ChangesQueue = new ConcurrentQueue<Change>(); 
    const string Destination = @"c:\Destination"; 
    const string Source = @"c:\Source"; 

    /// <summary> 
    /// Tests this instance. 
    /// </summary> 
    [Test] 
    public void Test() 
    { 
     var changesBackgroundWorker = new BackgroundWorker(); 
     changesBackgroundWorker.DoWork += ChangesBackgroundWorkerOnDoWork; 
     changesBackgroundWorker.RunWorkerAsync(); 
     var fileSystemWatcher = new FileSystemWatcher 
           { 
            Path = Source, 
            EnableRaisingEvents = true, 
            IncludeSubdirectories = true, 
            NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.LastAccess | NotifyFilters.CreationTime | NotifyFilters.FileName | NotifyFilters.DirectoryName, 
            InternalBufferSize = 65536 
           }; 
     fileSystemWatcher.Created += FileSystemWatcherOnCreated; 
     fileSystemWatcher.Deleted += FileSystemWatcherOnDeleted; 
     fileSystemWatcher.Renamed += FileSystemWatcherOnRenamed; 
     fileSystemWatcher.Error += FileSystemWatcherOnError; 

     while (true) 
     Thread.Sleep(1000000); 
    } 

    /// <summary> 
    /// Changeses the background worker configuration document work. 
    /// </summary> 
    /// <param name="sender">The sender.</param> 
    /// <param name="doWorkEventArgs">The <see cref="DoWorkEventArgs"/> instance containing the event data.</param> 
    /// <exception cref="System.ArgumentOutOfRangeException"></exception> 
    private static void ChangesBackgroundWorkerOnDoWork(object sender, DoWorkEventArgs doWorkEventArgs) 
    { 
     while (true) 
     { 
     Change change; 
     if (ChangesQueue.TryDequeue(out change)) 
     { 
      var backgroundWorker = new BackgroundWorker(); 
      switch (change.ChangeType) 
      { 
      case WatcherChangeTypes.Created: 
       backgroundWorker.DoWork += (o, args) => 
              { 
              var fileSystemType = GetFileSystemType(change.FullPath); 

              var newItem = Path.Combine(Destination, change.Name); 
              while (true) 
              { 
               try 
               { 
               switch (fileSystemType) 
               { 
                case FileSystemType.File: 
                File.Copy(change.FullPath, newItem, true); 
                break; 
                case FileSystemType.Directory: 
                var directorySecurity = 
                 Directory.GetAccessControl(change.FullPath); 
                Directory.CreateDirectory(newItem, directorySecurity); 
                break; 
                case FileSystemType.NotExistant: 
                break; 
               } 
               return; 
               } 
               catch (IOException exception) 
               { 
               Thread.Sleep(100); 
               Console.WriteLine(exception.Message); 
               } 
              } 
              }; 
       break; 
      case WatcherChangeTypes.Deleted: 
       backgroundWorker.DoWork += (o, args) => 
       { 
       var itemToDelete = Path.Combine(Destination, change.Name); 
       var fileSystemType = GetFileSystemType(itemToDelete); 
       switch (fileSystemType) 
       { 
        case FileSystemType.File: 
        File.Delete(itemToDelete); 
        break; 
        case FileSystemType.Directory: 
        Directory.Delete(itemToDelete, true); 
        break; 
       } 
       }; 
       break; 
      case WatcherChangeTypes.Changed: 
       backgroundWorker.DoWork += (o, args) => 
       { 
       var fileSystemType = GetFileSystemType(change.FullPath); 
       var newItem = Path.Combine(Destination, change.Name); 
       switch (fileSystemType) 
       { 
        case FileSystemType.File: 
        File.Copy(change.FullPath, newItem, true); 
        break; 
       } 
       }; 
       break; 
      case WatcherChangeTypes.Renamed: 
       backgroundWorker.DoWork += (o, args) => 
       { 
       var fileSystemType = GetFileSystemType(change.FullPath); 
       var oldItem = Path.Combine(Destination, change.OldName); 
       var newItem = Path.Combine(Destination, change.Name); 
       switch (fileSystemType) 
       { 
        case FileSystemType.File: 
        if (File.Exists(oldItem)) 
         File.Move(oldItem, newItem); 
        break; 
        case FileSystemType.Directory: 
        if (Directory.Exists(oldItem)) 
         Directory.Move(oldItem, newItem); 
        break; 
       } 
       }; 
       break; 
      case WatcherChangeTypes.All: 
       break; 
      default: 
       throw new ArgumentOutOfRangeException(); 
      } 
      backgroundWorker.RunWorkerAsync(); 
     } 
     } 
    } 

    /// <summary> 
    /// Files the system watcher configuration created. 
    /// </summary> 
    /// <param name="sender">The sender.</param> 
    /// <param name="fileSystemEventArgs">The <see cref="FileSystemEventArgs"/> instance containing the event data.</param> 
    private static void FileSystemWatcherOnCreated(object sender, FileSystemEventArgs fileSystemEventArgs) 
    { 
     ChangesQueue.Enqueue(new Change 
     { 
     ChangeType = WatcherChangeTypes.Created, 
     FullPath = fileSystemEventArgs.FullPath, 
     Name = fileSystemEventArgs.Name 
     }); 
    } 

    /// <summary> 
    /// Files the system watcher configuration deleted. 
    /// </summary> 
    /// <param name="sender">The sender.</param> 
    /// <param name="fileSystemEventArgs">The <see cref="FileSystemEventArgs"/> instance containing the event data.</param> 
    private static void FileSystemWatcherOnDeleted(object sender, FileSystemEventArgs fileSystemEventArgs) 
    { 
     ChangesQueue.Enqueue(new Change 
     { 
     ChangeType = WatcherChangeTypes.Deleted, 
     FullPath = fileSystemEventArgs.FullPath, 
     Name = fileSystemEventArgs.Name 
     }); 
    } 

    /// <summary> 
    /// Files the system watcher configuration error. 
    /// </summary> 
    /// <param name="sender">The sender.</param> 
    /// <param name="errorEventArgs">The <see cref="ErrorEventArgs"/> instance containing the event data.</param> 
    private static void FileSystemWatcherOnError(object sender, ErrorEventArgs errorEventArgs) 
    { 
     var exception = errorEventArgs.GetException(); 
     Console.WriteLine(exception.Message); 
    } 

    /// <summary> 
    /// Files the system watcher configuration renamed. 
    /// </summary> 
    /// <param name="sender">The sender.</param> 
    /// <param name="fileSystemEventArgs">The <see cref="RenamedEventArgs"/> instance containing the event data.</param> 
    private static void FileSystemWatcherOnRenamed(object sender, RenamedEventArgs fileSystemEventArgs) 
    { 
     ChangesQueue.Enqueue(new Change 
     { 
     ChangeType = WatcherChangeTypes.Renamed, 
     FullPath = fileSystemEventArgs.FullPath, 
     Name = fileSystemEventArgs.Name, 
     OldFullPath = fileSystemEventArgs.OldFullPath, 
     OldName = fileSystemEventArgs.OldName 
     }); 
    } 

    /// <summary> 
    /// Gets the type of the file system. 
    /// </summary> 
    /// <param name="fullPath">The full path.</param> 
    /// <returns></returns> 
    private static FileSystemType GetFileSystemType(string fullPath) 
    { 
     if (Directory.Exists(fullPath)) 
     return FileSystemType.Directory; 
     if (File.Exists(fullPath)) 
     return FileSystemType.File; 
     return FileSystemType.NotExistant; 
    } 
    } 

    /// <summary> 
    /// Type of file system object 
    /// </summary> 
    internal enum FileSystemType 
    { 
    /// <summary> 
    /// The file 
    /// </summary> 
    File, 
    /// <summary> 
    /// The directory 
    /// </summary> 
    Directory, 
    /// <summary> 
    /// The not existant 
    /// </summary> 
    NotExistant 
    } 

    /// <summary> 
    /// Change information 
    /// </summary> 
    public class Change 
    { 
    /// <summary> 
    /// Gets or sets the type of the change. 
    /// </summary> 
    /// <value> 
    /// The type of the change. 
    /// </value> 
    public WatcherChangeTypes ChangeType { get; set; } 

    /// <summary> 
    /// Gets or sets the full path. 
    /// </summary> 
    /// <value> 
    /// The full path. 
    /// </value> 
    public string FullPath { get; set; } 

    /// <summary> 
    /// Gets or sets the name. 
    /// </summary> 
    /// <value> 
    /// The name. 
    /// </value> 
    public string Name { get; set; } 

    /// <summary> 
    /// Gets or sets the old full path. 
    /// </summary> 
    /// <value> 
    /// The old full path. 
    /// </value> 
    public string OldFullPath { get; set; } 

    /// <summary> 
    /// Gets or sets the old name. 
    /// </summary> 
    /// <value> 
    /// The old name. 
    /// </value> 
    public string OldName { get; set; } 
    } 
} 
5

Có hai điều bạn nên làm:

  1. Set InternalBufferSize đến mức tối đa giá trị hỗ trợ (65536). Nỗ lực của bạn để đặt thành "8192 * 128" lớn hơn giá trị được hỗ trợ tối đa được liệt kê trong documentation, vì vậy bạn có thể không tăng kích thước bộ đệm.
  2. Sự kiện xếp hàng từ FileSystemWatcher vào chuỗi nền để xử lý.

Đây là điểm thứ hai ở đây không được hiểu rõ và thực sự cần được ghi lại trên MSDN. Bên trong, FileSystemWatcher đang xếp hàng các sự kiện thay đổi vào bộ đệm trong đó bạn đặt kích thước ở trên. Tuy nhiên, các mục chỉ bị xóa khỏi bộ đệm sau khi trình xử lý sự kiện của bạn trả về. Điều này có nghĩa là mọi chu kỳ của việc xử lý sự kiện trên đầu của bạn sẽ làm tăng khả năng đệm lấp đầy. Những gì bạn cần làm là xóa hàng đợi có giới hạn của FileSystemWatcher càng nhanh càng tốt và di chuyển các sự kiện vào hàng đợi vô hạn của riêng bạn, để xử lý ở tốc độ bạn có thể xử lý hoặc loại bỏ nếu bạn quan tâm, nhưng với một số thông tin xung quanh nó.

Đây là những gì tôi làm trong mã của mình. Trước tiên, tôi bắt đầu chuỗi điều phối của riêng mình:

Dispatcher changeDispatcher = null; 
ManualResetEvent changeDispatcherStarted = new ManualResetEvent(false); 
Action changeThreadHandler =() => 
{ 
    changeDispatcher = Dispatcher.CurrentDispatcher; 
    changeDispatcherStarted.Set(); 
    Dispatcher.Run(); 
}; 
new Thread(() => changeThreadHandler()) { IsBackground = true }.Start(); 
changeDispatcherStarted.WaitOne(); 

Sau đó, tôi tạo trình theo dõi. Lưu ý kích thước bộ đệm được thiết lập. Trong trường hợp của tôi, tôi chỉ xem những thay đổi trong thư mục đích, không phải thư mục con:

FileSystemWatcher watcher = new FileSystemWatcher(); 
watcher.Path = path; 
watcher.InternalBufferSize = 64 * 1024; 
watcher.IncludeSubdirectories = false; 

Bây giờ tôi đính kèm xử lý sự kiện của tôi, nhưng ở đây tôi gọi chúng vào điều phối của tôi thay vì chạy chúng đồng bộ trong các chủ đề quan sát. Vâng, các sự kiện sẽ được xử lý theo thứ tự của các điều phối:

watcher.Changed += (sender, e) => changeDispatcher.BeginInvoke(new Action(() => OnChanged(sender, e))); 
watcher.Created += (sender, e) => changeDispatcher.BeginInvoke(new Action(() => OnCreated(sender, e))); 
watcher.Deleted += (sender, e) => changeDispatcher.BeginInvoke(new Action(() => OnDeleted(sender, e))); 
watcher.Renamed += (sender, e) => changeDispatcher.BeginInvoke(new Action(() => OnRenamed(sender, e))); 

Và cuối cùng, sau khi xử lý các FileSystemWatcher (? Bạn đã làm điều đó, bên phải), bạn cần phải tắt phối của bạn:

watcher.Dispose() 
changeDispatcher.BeginInvokeShutdown(DispatcherPriority.Normal); 

Và đó là nó. Tôi đã nhận được vấn đề này bản thân mình, cả trong các kịch bản mạng và địa phương. Sau khi sử dụng cách tiếp cận này, tôi đã không thể tạo lại lỗi này, ngay cả khi đang bung ra các tệp rỗng để xem các thư mục nhanh nhất có thể. Nếu bạn đã từng quản lý bằng cách nào đó xả bộ đệm trong trường hợp này (mà tôi không chắc chắn là có thể, API thượng nguồn có lẽ là chậm hơn), vẫn còn có chỗ để tối ưu hóa ở đây. Miễn là dispatcher của bạn là qua "điểm tới hạn", mặc dù, người gửi không thể gửi các sự kiện nhanh hơn bạn có thể gửi chúng, bạn sẽ không bao giờ nhận được một backlog, và do đó không bao giờ thổi bộ đệm. Tôi tin rằng cách tiếp cận này đặt bạn vào khu vực an toàn đó.

+0

Và tôi vừa mới nhận thấy Luis về cơ bản đã nói điều tương tự hơn hai năm trước. Ồ, đây là một ví dụ về mã thay thế, và ví dụ này đang được sử dụng trong môi trường sản xuất. –