2009-03-31 26 views
36

Tôi đang gặp sự cố với FileSystemWatcher khi nhiều tệp được đặt vào thư mục đã xem. Tôi muốn phân tích cú pháp tệp ngay khi được đặt trong thư mục. Thông thường, tệp đầu tiên phân tích cú pháp tốt, nhưng thêm tệp thứ hai vào thư mục sẽ gây ra sự cố truy cập. Đôi khi, tệp đầu tiên thậm chí không phân tích cú pháp. Chỉ có một ứng dụng đang chạy và xem thư mục này. Cuối cùng, quá trình này sẽ chạy trên nhiều máy và họ sẽ xem một thư mục được chia sẻ nhưng chỉ có một máy chủ có thể phân tích từng tệp khi dữ liệu được nhập vào cơ sở dữ liệu và không có khóa chính.Lỗi truy cập tệp bằng FileSystemWatcher khi nhiều tệp được thêm vào thư mục

Đây là mã FileSystemWatcher:

public void Run() { 
    FileSystemWatcher watcher = new FileSystemWatcher("C:\\temp"); 
    watcher.NotifyFilter = NotifyFilters.FileName; 
    watcher.Filter = "*.txt"; 

    watcher.Created += new FileSystemEventHandler(OnChanged); 

    watcher.EnableRaisingEvents = true; 
    System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite); 
} 

Sau đó, phương pháp mà phân tích các file:

private void OnChanged(object source, FileSystemEventArgs e) { 
    string line = null; 

    try { 
    using (FileStream fs = new FileStream(e.FullPath, FileMode.Open, FileAccess.Read, FileShare.None)) { 
     using (StreamReader sr = new StreamReader(fs)) { 
     while (sr.EndOfStream == false) { 
      line = sr.ReadLine(); 
      //parse the line and insert into the database 
     } 
     } 
    } 
    } 
    catch (IOException ioe) { 
    Console.WriteLine("OnChanged: Caught Exception reading file [{0}]", ioe.ToString()); 
    } 

Khi di chuyển các tập tin thứ hai, nó được bắt

System.IO .IOException: Quá trình không thể truy cập tệp 'C: \ Temp \ TestFile.txt' vì nó đang được sử dụng bởi một tiến trình khác.

Tôi hy vọng sẽ thấy lỗi này nếu nó đang chạy trên nhiều máy nhưng hiện chỉ chạy trên một máy chủ. Không nên có một quá trình khác bằng cách sử dụng tệp này - tôi đã tạo và sao chép chúng vào thư mục khi ứng dụng đang chạy.

Đây có phải là cách thích hợp để thiết lập FileSystemWatcher không? Làm thế nào tôi có thể xem những gì có khóa trên tập tin này? Tại sao nó không phân tích cả hai tệp - tôi có phải đóng FileStream không? Tôi muốn giữ tùy chọn FileShare.None bởi vì tôi chỉ muốn một máy chủ phân tích cú pháp tệp - máy chủ nhận tệp đầu tiên phân tích cú pháp tệp đó.

Trả lời

50

Một vấn đề điển hình của phương pháp này là tệp vẫn đang được sao chép trong khi sự kiện được kích hoạt. Rõ ràng, bạn sẽ nhận được một ngoại lệ vì tập tin bị khóa trong khi sao chép. Một ngoại lệ đặc biệt có khả năng trên các tệp lớn.

Để giải quyết sự cố, trước tiên bạn có thể sao chép tệp rồi đổi tên tệp và lắng nghe sự kiện đổi tên.

Hoặc một tùy chọn khác là có một vòng lặp while kiểm tra xem tệp có thể được mở bằng quyền ghi hay không. Nếu nó có thể bạn sẽ biết rằng sao chép đã được hoàn thành. C# mã có thể giống như thế này (trong một hệ thống sản xuất bạn có thể muốn có một số lượng tối đa lần thử lại hoặc thời gian chờ thay vì một while(true)):

/// <summary> 
/// Waits until a file can be opened with write permission 
/// </summary> 
public static void WaitReady(string fileName) 
{ 
    while (true) 
    { 
     try 
     { 
      using (Stream stream = System.IO.File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)) 
      { 
       if (stream != null) 
       { 
        System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} ready.", fileName)); 
        break; 
       } 
      } 
     } 
     catch (FileNotFoundException ex) 
     { 
      System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} not yet ready ({1})", fileName, ex.Message)); 
     } 
     catch (IOException ex) 
     { 
      System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} not yet ready ({1})", fileName, ex.Message)); 
     } 
     catch (UnauthorizedAccessException ex) 
     { 
      System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} not yet ready ({1})", fileName, ex.Message)); 
     } 
     Thread.Sleep(500); 
    } 
} 

Tuy nhiên, cách tiếp cận khác sẽ được đặt một tập tin kích hoạt nhỏ trong thư mục sau khi sao chép xong. FileSystemWatcher của bạn sẽ chỉ nghe tệp kích hoạt.

+0

Các tệp thử nghiệm này rất nhỏ - chỉ một vài dòng văn bản ngắn, do đó không được khóa quá lâu trong khi sao chép. Làm thế nào tôi sẽ lặp lại để kiểm tra xem tập tin đã sẵn sàng để ghi vào? –

+0

Cảm ơn! Tôi xấu hổ vì tôi không thể nghĩ ra một vòng lặp trong khi giải pháp cho điều này - tôi cần đọc phiên bản cập nhật của tệp đầu ra của một ứng dụng khác. Tôi nhận được nhiều sự kiện cho mỗi lần viết - vì vậy tôi đã sử dụng bộ hẹn giờ được đặt lại mỗi lần và đợi X giây trước khi kích hoạt mã đọc tệp được cập nhật. Nhưng sau đó tôi bị mất cập nhật nếu ứng dụng đã viết hai lần trong vòng X giây. – Gishu

+3

+1 Nhưng bạn thực sự cần có thời gian chờ đợi tối đa để quá trình này không bị khóa mãi mãi –

0

Tôi gặp vấn đề tương tự trong DFS. Độ phân giải của tôi đạt được bằng cách thêm hai dòng trống vào mỗi tệp. Sau đó, mã của tôi chờ hai dòng trống trong tệp. Sau đó, tôi có chắc chắn để đọc toàn bộ dữ liệu từ tập tin.

3

Khi bạn mở tệp theo phương pháp OnChanged, bạn đang chỉ định FileShare.None, theo the documentation, sẽ gây bất kỳ nỗ lực nào khác để mở tệp không thành công khi bạn mở tệp. Vì tất cả các bạn (và người quan sát của bạn) đang làm là đọc, hãy thử sử dụng FileShare.Read để thay thế.

+0

Các giải pháp thực sự mà không có hacks ngớ ngẩn. –

2

Giải pháp đơn giản là vứt bỏ đồng hồ hệ thống tập tin khi bạn nhận được thông báo. trước khi sao chép các tập tin, làm cho các chủ đề hiện tại chờ đợi cho đến khi nó nhận được sự kiện xử lý filesystemwatcher. sau đó bạn có thể tiếp tục sao chép tệp đã thay đổi mà không gặp sự cố truy cập. Tôi đã có yêu cầu tương tự và tôi đã làm nó chính xác như những gì tôi đã đề cập. nó đã làm việc.

Ví dụ Code:

public void TestWatcher() 
{ 
    using (var fileWatcher = new FileSystemWatcher()) 
    { 

     string path = @"C:\sv"; 
     string file = "pos.csv"; 

     fileWatcher.Path = path; 
     fileWatcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite; 
     fileWatcher.Filter = file; 

     System.EventHandler onDisposed = (sender,args) => 
     { 
      eve.Set(); 
     }; 

     FileSystemEventHandler onFile = (sender, fileChange) => 
     { 
      fileWatcher.EnableRaisingEvents = false; 
      Thread t = new Thread(new ParameterizedThreadStart(CopyFile)); 
      t.Start(fileChange.FullPath); 
      if (fileWatcher != null) 
      { 
       fileWatcher.Dispose(); 
      } 
      proceed = false; 
     }; 

     fileWatcher.Changed += onFile; 
     fileWatcher.Created += onFile; 
     fileWatcher.Disposed+= onDisposed; 
     fileWatcher.EnableRaisingEvents = true; 

     while (proceed) 
     { 
      if (!proceed) 
      { 
       break; 
      } 
     } 
    } 
} 

public void CopyFile(object sourcePath) 
{ 
    eve.WaitOne(); 
    var destinationFilePath = @"C:\sv\Co"; 
    if (!string.IsNullOrEmpty(destinationFilePath)) 
    { 
     if (!Directory.Exists(destinationFilePath)) 
     { 
      Directory.CreateDirectory(destinationFilePath); 
     } 
     destinationFilePath = Path.Combine(destinationFilePath, "pos.csv"); 
    }   

    File.Copy((string)sourcePath, destinationFilePath); 
} 
1

tôi cảm thấy một ví dụ tốt về những gì bạn muốn là ConfigureAndWatchHandler trong log4net. Họ sử dụng bộ hẹn giờ để kích hoạt sự kiện xử lý tệp. Tôi cảm thấy điều này kết thúc là một thực hiện sạch hơn của vòng lặp while trong bài viết của 0xA3. Đối với những người bạn không muốn sử dụng dotPeek để kiểm tra tệp, tôi sẽ cố gắng cung cấp cho bạn một đoạn mã tại đây dựa trên mã OP:

private System.Threading.Timer _timer;  

public void Run() { 
    //setup filewatcher 
    _timer = new System.Threading.Timer(new TimerCallback(OnFileChange), (object) null, -1, -1); 
} 

private void OnFileChange(object state) 
{ 
    try 
    { 
    //handle files 
    } 
    catch (Exception ex) 
    { 
     //log exception 
     _timer.Change(500, -1); 
    } 
} 
9

Tôi đã để lại nhận xét ở trên, nhưng Tôi chưa có đủ điểm.

Câu trả lời hàng đầu đánh giá cho câu hỏi này có một khối mã mà trông như thế này:

using (Stream stream = System.IO.File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)) 
{ 
    if (stream != null) 
    { 
     System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} ready.", fileName)); 
     break; 
    } 
} 

Vấn đề với việc sử dụng FileShare.ReadWrite thiết lập là nó đang yêu cầu quyền truy cập vào các tập tin cơ bản nói "Tôi muốn đọc/ghi vào tập tin này, nhưng những người khác cũng có thể đọc/ghi vào nó. " Cách tiếp cận này không thành công trong tình huống của chúng tôi. Quá trình nhận được chuyển từ xa đã không đặt một khóa trên tập tin, nhưng nó đã tích cực viết cho nó. Mã hạ lưu của chúng tôi (SharpZipLib) đã thất bại với ngoại lệ "tệp đang sử dụng" vì nó đang cố mở tệp bằng FileShare.Read ("Tôi muốn tệp để đọc và chỉ cho phép các quy trình khác đọc"). Bởi vì quá trình đã mở tệp đã được viết cho nó, yêu cầu này không thành công.

Tuy nhiên, mã trong phản hồi ở trên quá thoải mái. Bằng cách sử dụng FileShare.ReadWrite, nó đã thành công trong việc có được quyền truy cập vào tệp (vì nó đã yêu cầu một hạn chế Chia sẻ có thể được vinh danh), nhưng cuộc gọi hạ lưu tiếp tục thất bại.

Thiết lập phần trong cuộc gọi đến File.Open nên một trong hai FileShare.Read hoặc FileShare.None, và KHÔNGFileShare.ReadWrite.

2

FileSystemWatcher kích hoạt watcher. Sự kiện đã xử lý hai lần cho mỗi lần tạo tệp đơn 1 khi bắt đầu sao chép tệp và lần thứ 2 khi sao chép tệp xong. Tất cả những gì bạn phải làm là bỏ qua sự kiện thứ nhất và sự kiện xử lý lần thứ hai.

Một ví dụ đơn giản về xử lý sự kiện:

private bool _fileCreated = false; 
private void FileSystemWatcher_FileCreated(object sender, FileSystemEventArgs e) 
{ 
    if (_fileCreated) 
    { 
     ReadFromFile();//just an example method call to access the new file 
    } 

    _fileCreated = !_fileCreated; 
} 
0
public static BitmapSource LoadImageNoLock(string path) 
{ 
    while (true) 
    { 
     try 
     { 
      var memStream = new MemoryStream(File.ReadAllBytes(path)); 
      var img = new BitmapImage(); 
      img.BeginInit(); 
      img.StreamSource = memStream; 
      img.EndInit(); 
      return img; 
      break; 
     } 
     catch (Exception ex) 
     { 
      Console.WriteLine(ex.Message); 
     } 
    } 
} 
1

Tôi có vấn đề tương tự. Nó chỉ vì FileSystemWatcher. Tôi vừa sử dụng
Thread.Sleep();

Và hiện tại nó hoạt động tốt. Khi tệp có trong thư mục, nó gọi onCreated hai lần. một lần khi tệp được sao chép và lần thứ hai khi sao chép hoàn tất. Cho rằng tôi đã sử dụng Thread.Ngủ(); Vì vậy, nó sẽ chờ trước khi tôi gọi ReadFile();

private static void OnCreated(object source, FileSystemEventArgs e) 
    { 
     try 
     { 
      Thread.Sleep(5000); 
      var data = new FileData(); 
      data.ReadFile(e.FullPath);     
     } 
     catch (Exception ex) 
     { 
      WriteLogforError(ex.Message, String.Empty, filepath); 
     } 
    } 
Các vấn đề liên quan