C# 等待文件被进程释放

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1406808/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 16:11:13  来源:igfitidea点击:

Wait for file to be freed by process

c#.netwinformsfile-ioioexception

提问by Refracted Paladin

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.

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

采纳答案by Gordon Thompson

A function like this will do it:

像这样的函数会做到这一点:

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

Stick it in a whileloop and you have something which will block until the file is accessible:

把它放在一个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)) { }
}

回答by JaredPar

There is no function out there which will allow you to wait on a particular handle / file system location to be available for writing. Sadly, all you can do is poll the handle for writing.

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

回答by tsilb

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

回答by tojo

You can let the System wait, until the process is closed.

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

Just as simple as this:

就这么简单:

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

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

回答by Almund

If you check access before writing to the file some other process might snatch the access again before you manage to do your write. Therefor I would suggest one of the following two:

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

  1. Wrap what you want to do in a retry scope that won't hide any other error
  2. Create a wrapper method that waits until you can get a stream and use that stream
  1. 将您想要执行的操作包装在不会隐藏任何其他错误的重试范围中
  2. 创建一个包装方法,等待您获得流并使用该流

getting a stream

获取流

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.");
}

then use it like this:

然后像这样使用它:

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

retry scope

重试范围

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.");
}

and then use WithRetry(() => File.WriteAllText(Path.Combine(_directory, name), contents));

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

回答by Matt Williams

Here is a solution that may be overkill for some users. I've created a new static class which has an event which is triggered only when the file finishes copying.

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

The user registers files which they would like to watch by calling FileAccessWatcher.RegisterWaitForFileAccess(filePath). If the file is not already being watched a new task is started which repeatedly checks the file to see if it can be opened. Each time it checks it also reads the file size. If the file size does not increase in a pre-defined time (5 minutes in my example) the loop is exited.

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

When the loop exits from the file being accessible or from the timeout the FileFinishedCopyingevent is triggered.

当循环从可访问的文件或超时退出时,将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;
    }
}

Example usage:

用法示例:

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

Handle the FileFinishedCopyingEvent:

处理 FileFinishedCopyingEvent:

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

回答by David Refaeli

You could use a lock statement with a Dummy variable, and it seems to work great.

您可以将 lock 语句与 Dummy 变量一起使用,它似乎效果很好。

Check here.

检查这里

回答by Davide Cannizzo

Using @Gordon Thompson 's answer, you have to create a loop such as the code below:

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

I found an optimized way that doesn't cause CPU overhead:

我找到了一种不会导致 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);

回答by Letum

Taking the top answer I wrote a similar one, but it's async, non-blocking, awaitable, cancelable (just stop the task) and checks the exception thrown.

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

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

You can use it in this fashion:

您可以以这种方式使用它:

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

If you have to really wait for it, or in a more JS-promise-way:

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

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