40

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

11 回答 11

71

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

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)) { }
}
于 2009-09-10T18:12:09.140 回答
21

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

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

于 2016-05-11T06:26:23.080 回答
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;
    }
}

示例用法:

// 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);
}
于 2016-10-14T04:35:40.470 回答
4

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

于 2009-09-10T18:06:44.413 回答
3

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

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-promise 的方式:

IsFileReady(path).ContinueWith(t => File.Delete(path));
于 2018-10-21T11:35:37.523 回答
2

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

就像这样简单:

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

于 2015-10-05T08:23:04.670 回答
1
bool isLocked = true;
while (isLocked)
 try {
  System.IO.File.Move(filename, filename2);
  isLocked = false;
 }
 catch { }
 System.IO.File.Move(filename2, filename);
于 2009-09-10T18:08:46.577 回答
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);
于 2018-02-16T20:30:11.500 回答
0

您可以使用带有 Dummy 变量的 lock 语句,它似乎工作得很好。

在这里检查。

于 2017-05-28T10:08:53.580 回答
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);
}
于 2020-07-06T20:32:31.137 回答
0

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

于 2020-07-19T03:43:40.727 回答