Подождите, пока файл будет освобожден процессом - PullRequest
32 голосов
/ 10 сентября 2009

Как мне дождаться освобождения файла, чтобы 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;
    }

Ответы [ 9 ]

55 голосов
/ 10 сентября 2009

Функция, подобная этой, сделает это:

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)) { }
}
13 голосов
/ 11 мая 2016

Если вы проверяете доступ перед записью в файл, другой процесс может перехватить доступ еще раз, прежде чем вам удастся выполнить запись. Для этого я бы предложил один из следующих двух:

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

3 голосов
/ 14 октября 2016

Вот решение, которое может быть излишним для некоторых пользователей. Я создал новый статический класс, у которого есть событие, которое запускается только после завершения копирования файла.

Пользователь регистрирует файлы, которые он хотел бы просмотреть, вызывая 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);
}
3 голосов
/ 10 сентября 2009

Там нет функции, которая позволила бы вам ждать, пока конкретный дескриптор / местоположение файловой системы будет доступно для записи. К сожалению, все, что вы можете сделать, это опросить ручку для записи.

2 голосов
/ 05 октября 2015

Систему можно подождать, пока процесс не будет закрыт.

Так же просто, как это:

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

2 голосов
/ 10 сентября 2009
bool isLocked = true;
while (isLocked)
 try {
  System.IO.File.Move(filename, filename2);
  isLocked = false;
 }
 catch { }
 System.IO.File.Move(filename2, filename);
1 голос
/ 16 февраля 2018

Используя ответ @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);
0 голосов
/ 21 октября 2018

Взяв верхний ответ, я написал аналогичный, но он асинхронный, неблокирующий, ожидаемый, отменяемый (просто остановите задачу) и проверяет выданное исключение.

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

Если вам действительно нужно его подождать, или в более обнадеживающем виде:

IsFileReady(path).ContinueWith(t => File.Delete(path));
0 голосов
/ 28 мая 2017

Вы можете использовать оператор блокировки с переменной Dummy, и, похоже, он отлично работает.

Отметьте здесь .

...