等待文件被进程释放

如何等待文件自由,以便ss.Save()可以用新的覆盖它。 如果我一起运行两次(ish)我得到一个generic GDI+错误。

  /// /// Grabs a screen shot of the App and saves it to the C drive in jpg /// 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; } 

像这样的函数会这样做:

 public static bool IsFileReady(string filename) { // 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(filename, FileMode.Open, FileAccess.Read, FileShare.None)) return inputStream.Length > 0; } catch (Exception) { return false; } } 

将它粘贴在while循环中,你会有一些东西会阻塞,直到文件可以访问:

 public static void WaitForFile(string filename) { //This will lock the execution until the file is ready //TODO: Add some logic to make it async and cancelable while (!IsFileReady(filename)) { } } 

如果在写入文件之前检查访问权限,则某些其他进程可能会在您进行写入之前再次获取访问权限。 因此我建议使用以下两种方法之一:

  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,name),contents));

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

这是一个对某些用户来说可能有点过分的解决方案。 我创建了一个新的静态类,它有一个只在文件完成复制时触发的事件。

用户通过调用FileAccessWatcher.RegisterWaitForFileAccess(filePath)来注册他们想要观看的文件。 如果文件尚未被监视,则启动新任务,重复检查文件以查看是否可以打开该文件。 每次检查时它也会读取文件大小。 如果文件大小没有在预定义的时间内增加(在我的示例中为5分钟),则退出循环。

当循环退出可访问的文件或从超时退出时,将触发FileFinishedCopying事件。

 public class FileAccessWatcher { // this list keeps track of files being watched private static ConcurrentDictionary watchedFiles = new ConcurrentDictionary(); 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(); } ///  /// Event triggered when the file is finished copying or when the file size has not increased in the last 5 minutes. ///  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); } 

没有任何function允许您等待特定的句柄/文件系统位置可用于写入。 可悲的是,你所能做的只是轮询句柄进行写作。

您可以让系统等待,直到该过程关闭。

就像这样简单:

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

你可以使用带有Dummy变量的lock语句,它看起来效果很好。

点击这里

使用@Gordon Thompson的答案,你必须创建一个循环,如下面的代码:

 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); 

我写了一个类似的答案,但它是异步,非阻塞,等待,可取消(只是停止任务)并检查抛出的exception。

 public static async Task IsFileReady(string filename) { await Task.Run(() => { if (!File.Exists(path)) { throw new IOException("File does not exist!"); } var isReady = false; while (!isReady) { // 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(filename, FileMode.Open, FileAccess.Read, FileShare.None)) isReady = inputStream.Length > 0; } catch (Exception e) { // Check if the exception is related to an IO error. if (e.GetType() == typeof(IOException)) { isReady = false; } else { // Rethrow the exception as it's not an exclusively-opened-exception. throw; } } } }); } 

你可以这种方式使用它:

 Task ready = IsFileReady(path); ready.Wait(1000); if (!ready.IsCompleted) { throw new FileLoadException($"The file {path} is exclusively opened by another process!"); } File.Delete(path); 

如果你必须真正等待它,或者以更多的JS承诺方式:

 IsFileReady(path).ContinueWith(t => File.Delete(path));