Wait for file to be freed by process

Ref*_*din 32 c# file-io ioexception winforms

How do I wait for the file to be free so that ss.Save() can overwrite it with a new one? If I run this twice close together(ish), I get a generic GDI+ error.

///<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;
}
Run Code Online (Sandbox Code Playgroud)

Gor*_*son 61

像这样的函数会这样做:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

将它粘在一个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)) { }
}
Run Code Online (Sandbox Code Playgroud)

  • 你也可以`return inputStream.Length> 0;`.我从不喜欢那些`if(condition)返回true; 否则返回false;`.. (70认同)
  • @Default我认为返回true/false更具可读性 (6认同)
  • -1因为:http://thedailywtf.com/Comments/The-Right-Way-to-Find-a-File.aspx#402913.正确的方法:http://stackoverflow.com/a/876513/160173 (6认同)
  • 它不应该在某个地方有一个 sleep() - 否则应用程序可能会变得无响应 (5认同)
  • 捕获所有异常是一种非常糟糕的做法,您应该更准确地了解文件根本无法访问的事实。 (3认同)

Alm*_*und 15

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

  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.");
}
Run Code Online (Sandbox Code Playgroud)

然后像这样使用它:

using (var stream = GetWriteStream("path"))
{
    using (var writer = new StreamWriter(stream))
        writer.Write("test");
}
Run Code Online (Sandbox Code Playgroud)

重试范围

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.");
}
Run Code Online (Sandbox Code Playgroud)

然后使用WithRetry(()=> File.WriteAllText(Path.Combine(_directory,name),contents));


Mat*_*ams 7

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

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

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

// 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);
Run Code Online (Sandbox Code Playgroud)

处理 FileFinishedCopyingEvent:

private void FileAccessWatcher_FileFinishedCopying(object sender, FileSystemEventArgs e)
{
    Console.WriteLine("File finished copying: " + e.FullPath);
}
Run Code Online (Sandbox Code Playgroud)


Jar*_*Par 5

没有任何函数可以让您等待特定句柄/文件系统位置可用于写入。遗憾的是,您所能做的就是轮询句柄以进行写入。