2011-01-13 155 views
25

我有一個作爲Windows服務運行的程序;它處理特定文件夾中的文件。由於它是一項服務,它會不斷監視文件夾以查找已添加的新文件。程序的一部分工作是對目標文件夾中的文件進行比較,並標記不匹配的文件。我想要做的是能夠檢測複製操作是否正在進行以及何時完成,這樣,如果文件尚未被複制到目標文件夾,則文件不會被過早標記。C# - 等待複製操作完成

我在想的是使用FileSystemWatcher觀察目標文件夾並查看是否正在進行復制操作。如果有,我把我的程序的主線程休眠,直到複製操作完成,然後繼續對文件夾執行正常操作。我只是想了解一下這種方法,看看它是否有效;如果其他人有任何其他獨特的方法來解決這個問題,將不勝感激。

UPDATE:

謝謝大家的建議

更新2:

我的混亂道歉,當我說目標目錄,我的意思是包含了所有我想要的文件的源文件夾處理。我的程序的一部分功能是將源目錄的目錄結構複製到目標目錄,並將所有有效的文件複製到該目標目錄,保留原始源目錄的目錄結構,即用戶可以複製包含文件的文件夾到源目錄。我想通過確保包含更多子文件夾和文件的一組新文件夾被複制到源目錄中進行處理來防止錯誤,直到複製過程完成後,我的程序纔會開始在目標目錄上運行。

+0

+1。這是一個非常好的問題。我還沒有想出一種不覺得像黑客的方法。 – David 2011-01-13 17:35:51

+1

這個問題是類似的,有一些很好的答案:http://stackoverflow.com/questions/30074/monitoring-files-how-to-know-when-a-file-is-complete – mfdoran 2011-01-13 17:38:20

回答

2

您在尋找的是典型的producer/consumer方案。你需要做的是'Producer/consumer queue'這部分page。這將允許您使用多線程(可能跨越背景工作)來複制文件,因此您不會阻止主服務線程偵聽系統事件&您可以在那裏執行更有意義的任務 - 如檢查新文件&更新隊列。所以on main thread do check for new filesbackground threads perform the actual coping task。根據個人經驗(已經實現這個任務),除非你在多CPU機器上運行,否則這種方法沒有太多的性能收益,但是這個過程非常乾淨。+代碼在邏輯上很好地分離。

總之,你所要做的就是有一個像下面的對象:

public class File 
{ 
    public string FullPath {get; internal set;} 
    public bool CopyInProgress {get; set;} // property to make sure 
    // .. other properties if desired 
} 

那麼上述問題發佈文件對象&隊列上的鎖來更新它&複製本教程以下。使用這種方法,您可以使用這個type approaches而不是持續監視文件複製完成。 在這裏認識到的重要一點是,你的服務有按實際物理文件File對象只有一個實例 - 只要確保你(1)鎖定您的隊列中添加&刪除&(2)鎖定實際File對象初始化更新時,當。

EDIT:以上,我說,如果你在一個單獨的線程這一做法「沒有太多的性能提升這一辦法,除非」我參考,來比較@傑森的建議這種方法必須是明顯更快,因爲@ Jason的解決方案執行非常昂貴的IO操作,這在大多數情況下會失敗。這我沒有測試,但我很確定,因爲我的方法不需要IO操作打開(僅限一次),流(僅限一次)&關閉文件(僅限一次)。 @Jason方法建議多開,開,開,開操作,這將全部失效除了最後一個。

10

是的,請使用FileSystemWatcher,但不要關注創建的事件,請注意更改的事件。每次觸發後,嘗試打開文件。像這樣:

var watcher = new FileSystemWatcher(path, filter); 
watcher.Changed += (sender, e) => { 
    FileStream file = null; 
    try { 
     Thread.Sleep(100); // hack for timing issues 
     file = File.Open(
      e.FullPath, 
      FileMode.Open, 
      FileAccess.Read, 
      FileShare.Read 
     ); 
    } 
    catch(IOException) { 
     // we couldn't open the file 
     // this is probably because the copy operation is not done 
     // just swallow the exception 
     return; 
    } 

    // now we have a handle to the file 
}; 

這是關於你可以做的最好的,不幸的是。有沒有乾淨的方式知道該文件已準備好供您使用。

+0

這是一個很值得我怎麼樣我也處理了它。對我來說,總是感覺到「Hack-ish」,但是+1,因爲我沒有更好的東西。 – David 2011-01-13 18:51:53

+1

@ivo s:低效?昂貴?誰在乎?它發生在後臺線程上,非常難以置信地成爲瓶頸。啊。此外,您正在假設他可以控制複製過程。請注意,這不是他的問題。 – jason 2011-01-14 15:30:20

2

一種方法是嘗試打開文件並查看是否出現錯誤。如果文件被複制,該文件將被鎖定。這將打開共享模式的文件,所以它會與該文件已打開寫鎖衝突:

using(System.IO.File.Open("file", FileMode.Open,FileAccess.Read, FileShare.Read)) {} 

另一種方法是檢查文件的大小。如果文件正被複制,它會隨時間而改變。

也可以獲得打開某個文件的所有應用程序的列表,但我不知道這個API。

1

我知道這是一個老問題,但這裏是我尋找的答案,只是這個問題後紡制了一個答案。這必須經過很多調整才能從我所從事的工作中去除一些專有權,所以這可能無法直接編譯,但它會給你一個想法。這對我很有用:



void BlockingFileCopySync(FileInfo original, FileInfo copyPath) 
{ 
    bool ready = false; 

    FileSystemWatcher watcher = new FileSystemWatcher(); 
    watcher.NotifyFilter = NotifyFilters.LastWrite; 
    watcher.Path = copyPath.Directory.FullName; 
    watcher.Filter = "*" + copyPath.Extension; 
    watcher.EnableRaisingEvents = true; 

    bool fileReady = false; 
    bool firsttime = true; 
    DateTime previousLastWriteTime = new DateTime(); 

    // modify this as you think you need to... 
    int waitTimeMs = 100; 

    watcher.Changed += (sender, e) => 
    { 
     // Get the time the file was modified 
     // Check it again in 100 ms 
     // When it has gone a while without modification, it's done. 
     while (!fileReady) 
     { 
      // We need to initialize for the "first time", 
      // ie. when the file was just created. 
      // (Really, this could probably be initialized off the 
      // time of the copy now that I'm thinking of it.) 
      if (firsttime) 
      { 
       previousLastWriteTime = System.IO.File.GetLastWriteTime(copyPath.FullName); 
       firsttime = false; 
       System.Threading.Thread.Sleep(waitTimeMs); 
       continue; 
      } 

      DateTime currentLastWriteTime = System.IO.File.GetLastWriteTime(copyPath.FullName); 

      bool fileModified = (currentLastWriteTime != previousLastWriteTime); 

      if (fileModified) 
      { 
       previousLastWriteTime = currentLastWriteTime; 
       System.Threading.Thread.Sleep(waitTimeMs); 
       continue; 
      } 
      else 
      { 
       fileReady = true; 
       break; 
      } 
     } 
    }; 

    System.IO.File.Copy(original.FullName, copyPath.FullName, true); 

    // This guy here chills out until the filesystemwatcher 
    // tells him the file isn't being writen to anymore. 
    while (!fileReady) 
    { 
     System.Threading.Thread.Sleep(waitTimeMs); 
    } 
}