Подождите, пока файл будет освобожден процессом

Как мне дождаться освобождения файла, чтобы ss.Save() мог заменить его новым? Если я запускаю это дважды близко друг к другу (иш), я получаю 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;
}

person Refracted Paladin    schedule 10.09.2009    source источник
comment
возможный дубликат Есть ли способ проверить, используется ли файл?   -  person Frédéric    schedule 25.09.2015
comment
В этом коде есть простая ошибка с File.Create(fileName). В ответах отсутствует этот момент. Не нужно ждать закрытия.   -  person usr    schedule 31.03.2016


Ответы (11)


Такая функция сделает это:

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)) { }
}
person Gordon Thompson    schedule 10.09.2009
comment
-1 потому что: thedailywtf.com/Comments/. Правильный способ: stackoverflow.com/a/876513/160173 - person David Murdoch; 07.11.2013
comment
Перехват всех исключений - очень плохая практика, вам следует быть более точным в том, что составляет факт, что файл просто недоступен. - person BartoszKP; 15.02.2018
comment
Разве он не должен где-то там спать () - иначе приложение может перестать отвечать - person Epirocks; 16.07.2018
comment
Также еще одна проблема с этим заключается в том, что вы входите в состояние гонки между возвратом ответа и другим фрагментом кода, открывающим файл, может войти другая система и заблокировать файл. - person greektreat; 10.09.2020

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

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

person Almund    schedule 11.05.2016
comment
Я также создал суть класса, обертывающего это поведение. Имейте в виду, конечно, что это может означать, что ваша архитектура имеет проблемы, если несколько классов читают и записывают в один и тот же файл конфликтующим образом. Таким образом вы можете потерять данные. gist.github.com/ddikman/667f309706fdf4f68b9fab2827b1bcca - person Almund; 11.05.2016
comment
Я не знаю, почему это не принятый ответ. Код намного безопаснее; вызов IsFileReady в while цикле, как предполагает ответ Гордона Томпсона, потенциально может потерпеть неудачу. Другой процесс может заблокировать файл между тем, когда условие цикла проверяет, доступен ли он, и вашим процессом, пытающимся получить к нему доступ. Единственное, e.HResult недоступен, потому что это protected. - person Mat Jones; 05.01.2017
comment
Спасибо за поддержку, хотя предлагаемое мной решение по сравнению с ним довольно загромождено. Однако мне не очень нравится внешний вид, поскольку в структуре нет встроенной поддержки, у вас остается несколько вариантов. Я использовал HResult, хотя, возможно, в разных версиях фреймворка он может отличаться, но я уверен, что есть еще какое-то свойство, которое можно использовать для определения того, какую ошибку содержит IOException. - person Almund; 07.01.2017
comment
Я знаю, что вы можете использовать свойство Message и сравнивать строки, но это уродливо IMO - person Mat Jones; 07.01.2017
comment
Я согласен, я бы не стал проверять сообщение без необходимости. Я дважды проверил MSDN о HResult, хотя в более поздних версиях фреймворка (после 3.5) он общедоступен. msdn.microsoft.com/en -us / library / - person Almund; 10.01.2017
comment
Хм действительно? Сначала я скопировал ваш код дословно (мне все равно пришлось немного подправить его для моего варианта использования), а IntelliSense Visual Studio выдал предупреждение / ошибку, что HResult был недоступен, потому что он был защищен, и я использую .NET 4.0. - person Mat Jones; 10.01.2017
comment
Вы правы, мне нужно это исправить, он общедоступен в .NET Framework (текущая версия), как указано на странице. Даже 4.0 защищена. - person Almund; 10.01.2017

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

Пользователь регистрирует файлы, которые он хотел бы просмотреть, вызывая 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);
}
person Matt Williams    schedule 14.10.2016
comment
Это красивое решение. На мой взгляд, это единственное пуленепробиваемое решение. Другие решения работают не во всех ситуациях. Например, это решение работает, когда: откройте 2 папки Windows, скопируйте файл из Dir a в наблюдаемый каталог. Это решение дает мне правильно один вызов события на FileFinishedCopying. Другие решения, такие как открытие файла / чтение файла, по-прежнему дают несколько совпадений в этом сценарии. Спасибо, Verry Mutch за это решение! - person Unnamed; 09.04.2020
comment
Судя по моим предварительным тестам, это работает хорошо, за исключением того, что экземпляр класса должен удалить себя из словаря watchedFiles, используя исходный путь к файлу, указанный при создании экземпляра, вместо this.file.FullName. Другими потенциальными улучшениями могут быть включение короткой задержки в цикл while RunLoop, чтобы другие процессы могли использовать ЦП, а также реализация токена отмены для немедленной остановки обработки. - person SteveC; 21.04.2021
comment
Кроме того, экземпляр класса FileAccessWatcher должен удаляться из словаря только после завершения RaiseFileFinishedCopyingEvent, если его обработка приводит к повторному добавлению файла в словарь. - person SteveC; 24.04.2021

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

person JaredPar    schedule 10.09.2009

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

Вот так просто:

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

person tojo    schedule 05.10.2015
comment
А кто должен закрыть (выйти) только что начатый процесс? - person astrowalker; 29.03.2019

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

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));
person Letum    schedule 21.10.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);
person Davide Cannizzo    schedule 16.02.2018
comment
SpinWait ставит процессор в очень тугую петлю, пока не выйдет из вращения. Это может не подходить для файловых операций! - person Sen Jacob; 19.03.2019

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

Проверьте здесь.

person Maverick Meerkat    schedule 28.05.2017

Проблема в том, что ваш код уже открывает файл, вызывая 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);
}
person Wim Coenen    schedule 06.07.2020

Одна практика, которую я использую, - это написать определенное слово в конце строки в файле. Тип Exit. Затем проверка того, заканчивается ли прочитанная строка словом Exit, означает, что файл был прочитан полностью.

person Community    schedule 19.07.2020
comment
Добавление оператора return также избавляет от проблемы, зачем использовать Exit вместо этого? - person HaseeB Mir; 19.07.2020

person    schedule
comment
Перемещение файла, чтобы узнать, заблокирован ли он, независимо от контекстной информации о назначении файла, не является хорошим подходом. - person Nick Bedford; 27.11.2018
comment
Возможно, тебе не захочется этого делать ^. Какой-то другой процесс может одновременно проверять, существует ли файл, и создавать другой или что-то еще. - person Angel; 17.06.2020