2010-06-16 75 views
1

我正在寫使用FileSystemWatcher監視更改爲指定目錄下的程序,當收到OnCreated或事件調用onChanged,它會將這些創建/修改過的文件到指定的目錄。起初我遇到的問題是,OnChanged/OnCreated事件可以發送兩次(如果需要處理500MB文件,則不可接受),但是我解決了這個問題,並且我真的被阻止,正在獲取以下內容IOException : 該進程無法訪問文件'C:\ Where \ Photosmarks \ bookmarks(11).html',因爲它正在被另一個進程使用。C#無法打開文件進行讀取

因此,防止複製它應該將所有文件的程序。 因此,正如我所提到的,當用戶使用此程序時,他/她指定了受監視的目錄,當用戶在該目錄中複製/創建/更改文件時,程序應該獲取OnCreated/OnChanged事件,然後將該文件複製到其他幾個目錄。 上述錯誤在所有情況下都會發生,如果用戶複製了幾個文件,這些文件需要覆蓋被監控文件夾中的其他文件或者複製多個文件的大量文件,甚至有時在受監控目錄中複製一個文件。 整個程序很大,所以我發送了最重要的部分。 OnCreated:

private void OnCreated(object source, FileSystemEventArgs e) { 
     AddLogEntry(e.FullPath, "created", ""); 

     // Update last access data if it's file so the same file doesn't 
     // get processed twice because of sending another event. 
     if (fileType(e.FullPath) == 2) { 
      lastPath = e.FullPath; 
      lastTime = DateTime.Now; 
     } 

     // serves no purpose now, it will be remove soon 
     string fileName = GetFileName(e.FullPath); 

     // copies file from source to few other directories 
     Copy(e.FullPath, fileName); 

     Console.WriteLine("OnCreated: " + e.FullPath); 
} 

調用onChanged:

private void OnChanged(object source, FileSystemEventArgs e) { 
    // is it directory 
    if (fileType(e.FullPath) == 1) 
     return; // don't mind directory changes itself 

    // Only if enough time has passed or if it's some other file 
    // because two events can be generated 
    int timeDiff = ((TimeSpan)(DateTime.Now - lastTime)).Seconds; 
    if ((timeDiff < minSecsDiff) && (e.FullPath.Equals(lastPath))) { 
     Console.WriteLine("-- skipped -- {0}, timediff: {1}", e.FullPath, timeDiff); 
     return; 
    } 

    // Update last access data for above to work 
    lastPath = e.FullPath; 
    lastTime = DateTime.Now; 

    // Only if size is changed, the rest will handle other handlers 
    if (e.ChangeType == WatcherChangeTypes.Changed) { 
     AddLogEntry(e.FullPath, "changed", ""); 
     string fileName = GetFileName(e.FullPath); 
     Copy(e.FullPath, fileName); 

     Console.WriteLine("OnChanged: " + e.FullPath); 
    } 
} 

的fileType:

private int fileType(string path) { 
    if (Directory.Exists(path)) 
     return 1; // directory 
    else if (File.Exists(path)) 
     return 2; // file 
    else 
     return 0; 
} 

複製:

private void Copy(string srcPath, string fileName) { 
    foreach (string dstDirectoy in paths) { 
     string eventType = "copied"; 
     string error = "noerror"; 
     string path = ""; 
     string dirPortion = ""; 

     // in case directory needs to be made 
     if (srcPath.Length > fsw.Path.Length) { 
      path = srcPath.Substring(fsw.Path.Length, 
        srcPath.Length - fsw.Path.Length); 

      int pos = path.LastIndexOf('\\'); 
      if (pos != -1) 
       dirPortion = path.Substring(0, pos); 
     } 

     if (fileType(srcPath) == 1) { 
      try { 
       Directory.CreateDirectory(dstDirectoy + path); 
       //Directory.CreateDirectory(dstDirectoy + fileName); 
       eventType = "created"; 
      } catch (IOException e) { 
       eventType = "error"; 
       error = e.Message; 
      } 
     } else { 
      try { 
       if (!overwriteFile && File.Exists(dstDirectoy + path)) 
        continue; 

       // create new dir anyway even if it exists just to be sure 
       Directory.CreateDirectory(dstDirectoy + dirPortion); 

       // copy file from where event occured to all specified directories 
       using (FileStream fsin = new FileStream(srcPath, FileMode.Open, FileAccess.Read, FileShare.Read)) { 
        using (FileStream fsout = new FileStream(dstDirectoy + path, FileMode.Create, FileAccess.Write)) { 
         byte[] buffer = new byte[32768]; 
         int bytesRead = -1; 

         while ((bytesRead = fsin.Read(buffer, 0, buffer.Length)) > 0) 
          fsout.Write(buffer, 0, bytesRead); 
        } 
       } 

      } catch (Exception e) { 
       if ((e is IOException) && (overwriteFile == false)) { 
        eventType = "skipped"; 
       } else { 
         eventType = "error"; 
         error = e.Message; 
         // attempt to find and kill the process locking the file. 
         // failed, miserably 
         System.Diagnostics.Process tool = new System.Diagnostics.Process(); 
         tool.StartInfo.FileName = "handle.exe"; 
         tool.StartInfo.Arguments = "\"" + srcPath + "\""; 
         tool.StartInfo.UseShellExecute = false; 
         tool.StartInfo.RedirectStandardOutput = true; 
         tool.Start(); 
         tool.WaitForExit(); 
         string outputTool = tool.StandardOutput.ReadToEnd(); 
         string matchPattern = @"(?<=\s+pid:\s+)\b(\d+)\b(?=\s+)"; 
         foreach (Match match in Regex.Matches(outputTool, matchPattern)) { 
          System.Diagnostics.Process.GetProcessById(int.Parse(match.Value)).Kill(); 
         } 

         Console.WriteLine("ERROR: {0}: [ {1} ]", e.Message, srcPath); 
       } 
      } 
     } 

     AddLogEntry(dstDirectoy + path, eventType, error); 
    } 
} 

我在我的程序檢查無處不在,每當我用一些文件我在using塊中使用它,所以即使將事件寫入日誌(由於可能存在過多的代碼,所以我省略了該類的內容)不會鎖定該文件,也就是說不應該這樣做,因爲所有操作都使用using語句塊。

我根本不知道誰是從鎖定用戶通過Windows或別的東西,如果不是我的節目「複製」過程中的文件。

現在我有兩個可能的「解決方案」(我不能說他們是乾淨的解決方案,因爲它們是黑客,因此不理想)。由於可能的問題是與fileType方法(還有什麼可以鎖定的文件?)我試了一下改變這一點,以模擬「堵,直到準備到打開」操作:

的fileType:

private int fileType(string path) { 
    FileStream fs = null; 
    int ret = 0; 
    bool run = true; 

    if (Directory.Exists(path)) 
     ret = 1; 
    else { 
     while (run) { 
      try { 
       fs = new FileStream(path, FileMode.Open); 
       ret = 2; 
       run = false; 
      } catch (IOException) { 
      } finally { 
       if (fs != null) { 
        fs.Close(); 
        fs.Dispose(); 
       } 
      } 
     } 
    } 

    return ret; 
} 

儘管我可以告訴(測試),但它的功能還是很強大的,更不用說其他缺陷了。

我可以嘗試的其他「解決方案」(我沒有測試它)在fileType()方法的末尾某處使用GC.Collect()。也許更糟糕的「解決方案」比以前更糟糕。

有人能認罪告訴我,究竟什麼是鎖定的文件,防止它打開,我該如何解決呢?我錯過了什麼?

在此先感謝。

回答

2

的問題是最有可能的是,文件至今仍然被複制,而你已經嘗試訪問它。這可能會發生在大文件上。

在實際開始處理之前,您可以嘗試檢查是否可以使用寫入權限打開文件。有關詳細信息如何執行該檢查here

如果您可以影響創建文件的過程,那麼可能會有更好的解決方案。首先使用臨時擴展名複製文件,然後在複製完成後重命名該文件,以便觸發FileSystemWatcher事件。

+0

非常感謝響應速度快,但我不需要寫權限,我需要_read_許可所以我可以從中讀取並複製到其他目的地。 – Maks 2010-06-16 21:17:48

+0

@Maks:當你可以寫文件時,你也可以閱讀它。如果您永遠無法寫信,例如因爲該文件夾是受保護的,您也可以簡單地嘗試以只讀方式打開文件。 – 2010-06-16 21:31:51

+0

我的意思是說,如果它是Windows進程(複製)而不是寫入權限(因爲它沒有在事件發生時完成),它可能「更容易」(比寫入權限更可能)獲取讀取權限:) – Maks 2010-06-17 13:22:06

1

您可以嘗試使用卷影副本。 有關更多詳細信息,請參閱www.codeproject.com/KB/dotnet/makeshadowcopy.aspx。

1

FileSystemWatcher事件在文件開始複製時觸發,而不是在結束時觸發,因此遇到這種錯誤是很常見的。

您的第一種方法可行,但是,我建議在另一個線程上旋轉I/O密集型代碼,並使用增量Sleep()而不是繁忙的等待。

但是,如果您有權訪問實際創建文件的軟件,則擴展名更改是稍微複雜一點的解決方案。只是要注意,即在FileSystemwatcher一個xls過濾器將匹配一個名爲myfile1.xls.temp文件,因爲我發現硬盤的方式:)

+0

謝謝你。正如我可以注意到的那樣,FSW在寫入文件(0字節)時觸發,然後在複製過程結束時觸發_second_ time,因爲文件大小將從0更改爲原始大小。 我想我需要用一些延遲時間的第一種方法,當然。如果我沒有找到鎖定文件以及如何解決這個問題,那麼這是我的最後手段。 – Maks 2010-06-16 21:59:04