等待文件被进程释放

时间:2009-09-10 18:02:22

标签: c# winforms file-io ioexception

如何等待文件空闲,以便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;
    }

11 个答案:

答案 0 :(得分:54)

这样的功能可以做到:

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 :(得分:13)

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

  1. 在不会隐藏任何其他错误的重试范围中包装您要执行的操作
  2. 创建一个等待直到获得流并使用该流的包装器方法
  3. 获取信息流

    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(()=&gt; File.WriteAllText(Path.Combine(_directory,name),contents));

答案 2 :(得分:3)

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

答案 3 :(得分:3)

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

用户通过调用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;
    }
}

使用示例:

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

答案 4 :(得分:2)

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

答案 5 :(得分:2)

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

就像这样简单:

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

答案 6 :(得分:1)

使用@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)) ;

我找到了一种不会导致CPU开销的优化方式:

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

答案 7 :(得分:0)

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

检查here

答案 8 :(得分:0)

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

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

答案 9 :(得分:0)

问题是您的代码已经通过调用File.Create打开了文件,该文件返回打开的文件流。根据时间的不同,垃圾收集器可能已经注意到返回的流没有被使用,并将其放在终结器队列中,然后终结器线程可能已经清理了一切,然后再次开始写入文件。但是,正如您所注意到的,这不能保证。

要解决此问题,您可以立即像File.Create(...).Dispose()一样再次关闭文件。或者,将流包装在using语句中,然后写入。

using (FileStream stream = 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(stream, ImageFormat.Jpeg);
}

答案 10 :(得分:0)

我使用的一种做法是在文件的字符串末尾写一个特定的单词。 输入“退出”。 然后检查读取的字符串是否以单词“ Exit”结尾意味着文件已被完全读取。