2009-09-10 70 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);