2009-09-10 72 views
22

如何等待文件爲空閒以便ss.Save()可以用新文件覆蓋文件。如果我將這兩次靠近在一起(ish),我會得到一個generic GDI+錯誤。等待文件被進程釋放

///<summary> 
    /// Grabs a screen shot of the App and saves it to the C drive in jpg 
    ///</summary> 
    private static String GetDesktopImage(DevExpress.XtraEditors.XtraForm whichForm) 
    { 
     Rectangle bounds = whichForm.Bounds; 

     // This solves my problem but creates a clutter issue 
     //var timeStamp = DateTime.Now.ToString("ddd-MMM-dd-yyyy-hh-mm-ss"); 
     //var fileName = "C:\\HelpMe" + timeStamp + ".jpg"; 

     var fileName = "C:\\HelpMe.jpg"; 
     File.Create(fileName); 
     using (Bitmap ss = new Bitmap(bounds.Width, bounds.Height)) 
     using (Graphics g = Graphics.FromImage(ss)) 
     { 
      g.CopyFromScreen(whichForm.Location, Point.Empty, bounds.Size); 
      ss.Save(fileName, ImageFormat.Jpeg); 
     } 

     return fileName; 
    } 
+3

可能重複[有沒有辦法檢查一個文件是否正在使用?](http://stackoverflow.com/questions/876473/is-there-a-way-to-check-if-a-file-is-in -use) – 2015-09-25 07:28:18

+0

這段代碼有一個'File.Create(fileName)'的簡單錯誤。答案缺少這一點。沒有必要等待關閉。 – usr 2016-03-31 11:14:10

回答

45

這樣的功能會做到這一點:

public static bool IsFileReady(String sFilename) 
    { 
     // If the file can be opened for exclusive access it means that the file 
     // is no longer locked by another process. 
     try 
     { 
      using (FileStream inputStream = File.Open(sFilename, FileMode.Open, FileAccess.Read, FileShare.None)) 
      { 
       if (inputStream.Length > 0) 
       { 
        return true; 
       } 
       else 
       { 
        return false; 
       } 

      } 
     } 
     catch (Exception) 
     { 
      return false; 
     } 
    } 

堅持在一個while循環,你有一些東西,將阻塞,直到該文件是可訪問的

+0

謝謝!我把它扔在那裏'var isReady = false; (!isReady) { isReady = IsFileReady(fileName); }' 一切似乎都很好。 – 2009-09-10 19:00:03

+61

你也可以'return inputStream.Length> 0;'。我從來不喜歡那些'if(condition)return true;否則返回false;'.. – Default 2012-06-11 14:55:40

+6

@Default我認爲返回真/假更可讀 – 2012-06-16 17:10:04

2

在那裏沒有任何功能可以讓您等待特定的句柄/文件系統位置可用於寫入。可悲的是,你所能做的只是輪詢寫作的手柄。

3
bool isLocked = true; 
while (isLocked) 
try { 
    System.IO.File.Move(filename, filename2); 
    isLocked = false; 
} 
catch { } 
System.IO.File.Move(filename2, filename); 
2

您可以讓系統等待,直到過程結束。

就這樣簡單:

Process.Start("the path of your text file or exe").WaitForExit();

8

如果檢查寫入文件中的一些其他進程可能會再次搶奪訪問你能做到你之前寫之前訪問。爲此我建議以下兩種方法之一:

  1. 總結要在重試範圍,不會隱藏任何其他錯誤
  2. 創建等待,直到你可以得到一個流的包裝方法做什麼,使用流

得到一個流

private FileStream GetWriteStream(string path, int timeoutMs) 
{ 
    var time = Stopwatch.StartNew(); 
    while (time.ElapsedMilliseconds < timeoutMs) 
    { 
     try 
     { 
      return new FileStream(path, FileMode.Create, FileAccess.Write); 
     } 
     catch (IOException e) 
     { 
      // access error 
      if (e.HResult != -2147024864) 
       throw; 
     } 
    } 

    throw new TimeoutException($"Failed to get a write handle to {path} within {timeoutMs}ms."); 
} 

然後使用它是這樣的:

using (var stream = GetWriteStream("path")) 
{ 
    using (var writer = new StreamWriter(stream)) 
     writer.Write("test"); 
} 

重試範圍

private void WithRetry(Action action, int timeoutMs = 1000) 
{ 
    var time = Stopwatch.StartNew(); 
    while(time.ElapsedMilliseconds < timeoutMs) 
    { 
     try 
     { 
      action(); 
      return; 
     } 
     catch (IOException e) 
     { 
      // access error 
      if (e.HResult != -2147024864) 
       throw; 
     } 
    } 
    throw new Exception("Failed perform action within allotted time."); 
} 

,然後使用WithRetry(()=> File.WriteAllText(Path.Combine(_directory,名),內容));

+0

我也爲包裝這種行爲的類創建了一個要點。請記住,這樣做可能意味着如果多個類以相沖突的方式讀取和寫入同一個文件,則您的體系結構會出現問題。你可能最終會以這種方式丟失數據。 https://gist.github.com/ddikman/667f309706fdf4f68b9fab2827b1bcca – Almund 2016-05-11 06:41:13

+0

我不知道爲什麼這不是被接受的答案。代碼更安全;正如Gordon Thompson的回答所暗示的,在'while'循環中調用'IsFileReady'可能會失敗。另一個進程可能會在循環條件檢查它是否可用並且您的進程試圖實際訪問它時介於兩者之間。只有「e.HResult」是無法訪問的,因爲它是受保護的。 – 2017-01-05 20:13:58

+0

感謝您的支持,雖然我的建議解決方案比較混亂。我不太喜歡它的外觀,但是由於在框架中沒有內置的支持,所以你只剩下幾個選項。儘管我使用了HResult,但在框架版本之間可能會有所不同,但我確定有一些其他屬性可用於檢測IOException包含的錯誤。 – Almund 2017-01-07 06:09:11

2

下面是一些解決方案,可能有些用戶矯枉過正。我創建了一個新的靜態類,它有一個只在文件完成複製時觸發的事件。

用戶通過調用FileAccessWatcher.RegisterWaitForFileAccess(filePath)來註冊他們想要觀看的文件。如果該文件尚未被監視,則開始一個新的任務,該任務重複檢查該文件以查看是否可以打開該文件。每次它檢查它也讀取文件大小。如果文件大小在預定義的時間內沒有增加(在我的例子中是5分鐘),則退出循環。

當循環從可訪問的文件退出或從超時中觸發事件時。

public class FileAccessWatcher 
{ 
    // this list keeps track of files being watched 
    private static ConcurrentDictionary<string, FileAccessWatcher> watchedFiles = new ConcurrentDictionary<string, FileAccessWatcher>(); 

    public static void RegisterWaitForFileAccess(string filePath) 
    { 
     // if the file is already being watched, don't do anything 
     if (watchedFiles.ContainsKey(filePath)) 
     { 
      return; 
     } 
     // otherwise, start watching it 
     FileAccessWatcher accessWatcher = new FileAccessWatcher(filePath); 
     watchedFiles[filePath] = accessWatcher; 
     accessWatcher.StartWatching(); 
    } 

    /// <summary> 
    /// Event triggered when the file is finished copying or when the file size has not increased in the last 5 minutes. 
    /// </summary> 
    public static event FileSystemEventHandler FileFinishedCopying; 

    private static readonly TimeSpan MaximumIdleTime = TimeSpan.FromMinutes(5); 

    private readonly FileInfo file; 

    private long lastFileSize = 0; 

    private DateTime timeOfLastFileSizeIncrease = DateTime.Now; 

    private FileAccessWatcher(string filePath) 
    { 
     this.file = new FileInfo(filePath); 
    } 

    private Task StartWatching() 
    { 
     return Task.Factory.StartNew(this.RunLoop); 
    } 

    private void RunLoop() 
    { 
     while (this.IsFileLocked()) 
     { 
      long currentFileSize = this.GetFileSize(); 
      if (currentFileSize > this.lastFileSize) 
      { 
       this.lastFileSize = currentFileSize; 
       this.timeOfLastFileSizeIncrease = DateTime.Now; 
      } 

      // if the file size has not increased for a pre-defined time limit, cancel 
      if (DateTime.Now - this.timeOfLastFileSizeIncrease > MaximumIdleTime) 
      { 
       break; 
      } 
     } 

     this.RemoveFromWatchedFiles(); 
     this.RaiseFileFinishedCopyingEvent(); 
    } 

    private void RemoveFromWatchedFiles() 
    { 
     FileAccessWatcher accessWatcher; 
     watchedFiles.TryRemove(this.file.FullName, out accessWatcher); 
    } 

    private void RaiseFileFinishedCopyingEvent() 
    { 
     FileFinishedCopying?.Invoke(this, 
      new FileSystemEventArgs(WatcherChangeTypes.Changed, this.file.FullName, this.file.Name)); 
    } 

    private long GetFileSize() 
    { 
     return this.file.Length; 
    } 

    private bool IsFileLocked() 
    { 
     try 
     { 
      using (this.file.Open(FileMode.Open)) { } 
     } 
     catch (IOException e) 
     { 
      var errorCode = Marshal.GetHRForException(e) & ((1 << 16) - 1); 

      return errorCode == 32 || errorCode == 33; 
     } 

     return false; 
    } 
} 

用法示例:

// register the event 
FileAccessWatcher.FileFinishedCopying += FileAccessWatcher_FileFinishedCopying; 

// start monitoring the file (put this inside the OnChanged event handler of the FileSystemWatcher 
FileAccessWatcher.RegisterWaitForFileAccess(fileSystemEventArgs.FullPath); 

處理好FileFinishedCopyingEvent:

private void FileAccessWatcher_FileFinishedCopying(object sender, FileSystemEventArgs e) 
{ 
    Console.WriteLine("File finished copying: " + e.FullPath); 
} 
0

你可以使用一個鎖語句一個虛擬變量,它似乎工作的偉大。

檢查here

0

使用@Gordon湯普森的答案,你必須創建一個循環,如下面的代碼:

public static bool IsFileReady(string sFilename) 
{ 
    try 
    { 
     using (FileStream inputStream = File.Open(sFilename, FileMode.Open, FileAccess.Read, FileShare.None)) 
      return inputStream.Length > 0; 
    } 
    catch (Exception) 
    { 
     return false; 
    } 
} 

while (!IsFileReady(yourFileName)) ; 

我發現了一個優化的方式,不會造成內存泄漏:

public static bool IsFileReady(this string sFilename) 
{ 
    try 
    { 
     using (FileStream inputStream = File.Open(sFilename, FileMode.Open, FileAccess.Read, FileShare.None)) 
      return inputStream.Length > 0; 
    } 
    catch (Exception) 
    { 
     return false; 
    } 
} 

SpinWait.SpinUntil(yourFileName.IsFileReady);