Вопрос

Я пишу программу на C #, которой необходимо повторно обращаться к 1 файлу изображения.Большую часть времени это работает, но если мой компьютер работает быстро, он попытается получить доступ к файлу до того, как он будет сохранен обратно в файловую систему, и выдаст ошибку: "Файл, используемый другим процессом".

Я хотел бы найти способ обойти это, но все мои поиски в Google привели только к созданию проверок с использованием обработки исключений.Это противоречит моей религии, поэтому мне было интересно, есть ли у кого-нибудь лучший способ сделать это?

Это было полезно?

Решение

Обновленное ПРИМЕЧАНИЕ по этому решению:Проверка с помощью FileAccess.ReadWrite завершится ошибкой для файлов, доступных только для чтения, поэтому решение было изменено для проверки с FileAccess.Read.В то время как это решение работает, потому что попытка проверить с FileAccess.Read завершится ошибкой, если файл имеет блокировку на запись или чтение, однако это решение не будет работать, если файл не имеет блокировки на запись или чтение, т.е.он был открыт (для чтения или записи) с помощью FileShare.Доступ для чтения или FileShare.Доступ на запись.

ОРИГИНАЛ: Я использую этот код в течение последних нескольких лет, и у меня не было с ним никаких проблем.

Понимаю ваши колебания по поводу использования исключений, но вы не можете избегать их все время:

protected virtual bool IsFileLocked(FileInfo file)
{
    FileStream stream = null;

    try
    {
        stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.None);
    }
    catch (IOException)
    {
        //the file is unavailable because it is:
        //still being written to
        //or being processed by another thread
        //or does not exist (has already been processed)
        return true;
    }
    finally
    {
        if (stream != null)
            stream.Close();
    }

    //file is not locked
    return false;
}

Другие советы

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

Ваш лучший выбор - попробовать catch / finally, который пытается получить дескриптор файла.

try
{
   using (Stream stream = new FileStream("MyFilename.txt", FileMode.Open))
   {
        // File/Stream manipulating code here
   }
} catch {
  //check here why it failed and ask user to retry if the file is in use.
}

Используйте это, чтобы проверить, заблокирован ли файл:

using System.IO;
using System.Runtime.InteropServices;
internal static class Helper
{
const int ERROR_SHARING_VIOLATION = 32;
const int ERROR_LOCK_VIOLATION = 33;

private static bool IsFileLocked(Exception exception)
{
    int errorCode = Marshal.GetHRForException(exception) & ((1 << 16) - 1);
    return errorCode == ERROR_SHARING_VIOLATION || errorCode == ERROR_LOCK_VIOLATION;
}

internal static bool CanReadFile(string filePath)
{
    //Try-Catch so we dont crash the program and can check the exception
    try {
        //The "using" is important because FileStream implements IDisposable and
        //"using" will avoid a heap exhaustion situation when too many handles  
        //are left undisposed.
        using (FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) {
            if (fileStream != null) fileStream.Close();  //This line is me being overly cautious, fileStream will never be null unless an exception occurs... and I know the "using" does it but its helpful to be explicit - especially when we encounter errors - at least for me anyway!
        }
    }
    catch (IOException ex) {
        //THE FUNKY MAGIC - TO SEE IF THIS FILE REALLY IS LOCKED!!!
        if (IsFileLocked(ex)) {
            // do something, eg File.Copy or present the user with a MsgBox - I do not recommend Killing the process that is locking the file
            return false;
        }
    }
    finally
    { }
    return true;
}
}

По соображениям производительности я рекомендую вам прочитать содержимое файла в ходе той же операции.Вот несколько примеров:

public static byte[] ReadFileBytes(string filePath)
{
    byte[] buffer = null;
    try
    {
        using (FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
        {
            int length = (int)fileStream.Length;  // get file length
            buffer = new byte[length];            // create buffer
            int count;                            // actual number of bytes read
            int sum = 0;                          // total number of bytes read

            // read until Read method returns 0 (end of the stream has been reached)
            while ((count = fileStream.Read(buffer, sum, length - sum)) > 0)
                sum += count;  // sum is a buffer offset for next reading

            fileStream.Close(); //This is not needed, just me being paranoid and explicitly releasing resources ASAP
        }
    }
    catch (IOException ex)
    {
        //THE FUNKY MAGIC - TO SEE IF THIS FILE REALLY IS LOCKED!!!
        if (IsFileLocked(ex))
        {
            // do something? 
        }
    }
    catch (Exception ex)
    {
    }
    finally
    {
    }
    return buffer;
}

public static string ReadFileTextWithEncoding(string filePath)
{
    string fileContents = string.Empty;
    byte[] buffer;
    try
    {
        using (FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
        {
            int length = (int)fileStream.Length;  // get file length
            buffer = new byte[length];            // create buffer
            int count;                            // actual number of bytes read
            int sum = 0;                          // total number of bytes read

            // read until Read method returns 0 (end of the stream has been reached)
            while ((count = fileStream.Read(buffer, sum, length - sum)) > 0)
            {
                sum += count;  // sum is a buffer offset for next reading
            }

            fileStream.Close(); //Again - this is not needed, just me being paranoid and explicitly releasing resources ASAP

            //Depending on the encoding you wish to use - I'll leave that up to you
            fileContents = System.Text.Encoding.Default.GetString(buffer);
        }
    }
    catch (IOException ex)
    {
        //THE FUNKY MAGIC - TO SEE IF THIS FILE REALLY IS LOCKED!!!
        if (IsFileLocked(ex))
        {
            // do something? 
        }
    }
    catch (Exception ex)
    {
    }
    finally
    { }     
    return fileContents;
}

public static string ReadFileTextNoEncoding(string filePath)
{
    string fileContents = string.Empty;
    byte[] buffer;
    try
    {
        using (FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
        {
            int length = (int)fileStream.Length;  // get file length
            buffer = new byte[length];            // create buffer
            int count;                            // actual number of bytes read
            int sum = 0;                          // total number of bytes read

            // read until Read method returns 0 (end of the stream has been reached)
            while ((count = fileStream.Read(buffer, sum, length - sum)) > 0) 
            {
                sum += count;  // sum is a buffer offset for next reading
            }

            fileStream.Close(); //Again - this is not needed, just me being paranoid and explicitly releasing resources ASAP

            char[] chars = new char[buffer.Length / sizeof(char) + 1];
            System.Buffer.BlockCopy(buffer, 0, chars, 0, buffer.Length);
            fileContents = new string(chars);
        }
    }
    catch (IOException ex)
    {
        //THE FUNKY MAGIC - TO SEE IF THIS FILE REALLY IS LOCKED!!!
        if (IsFileLocked(ex))
        {
            // do something? 
        }
    }
    catch (Exception ex)
    {
    }
    finally
    {
    }

    return fileContents;
}

Попробуйте это сами:

byte[] output1 = Helper.ReadFileBytes(@"c:\temp\test.txt");
string output2 = Helper.ReadFileTextWithEncoding(@"c:\temp\test.txt");
string output3 = Helper.ReadFileTextNoEncoding(@"c:\temp\test.txt");

Возможно, вы могли бы использовать Просмотр файловой системы и следите за Измененным событием.

Я сам этим не пользовался, но, возможно, попробовать стоит.Если filesystemwatcher окажется немного тяжеловат для этого случая, я бы выбрал цикл try / catch / sleep .

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

Используйте приведенную ниже функцию, например

TimeoutFileAction(() => { System.IO.File.etc...; return null; } );

Повторно используемый метод, время ожидания которого истекает через 2 секунды

private T TimeoutFileAction<T>(Func<T> func)
{
    var started = DateTime.UtcNow;
    while ((DateTime.UtcNow - started).TotalMilliseconds < 2000)
    {
        try
        {
            return func();                    
        }
        catch (System.IO.IOException exception)
        {
            //ignore, or log somewhere if you want to
        }
    }
    return default(T);
}

единственный известный мне способ - это использовать Win32 exclusive lock API, который не слишком быстр, но примеры существуют.

Большинство людей, для простого решения этой проблемы, просто пробуют циклы try / catch / sleep.

static bool FileInUse(string path)
    {
        try
        {
            using (FileStream fs = new FileStream(path, FileMode.OpenOrCreate))
            {
                fs.CanWrite
            }
            return false;
        }
        catch (IOException ex)
        {
            return true;
        }
    }

string filePath = "C:\\Documents And Settings\\yourfilename";
bool isFileInUse;

isFileInUse = FileInUse(filePath);

// Then you can do some checking
if (isFileInUse)
   Console.WriteLine("File is in use");
else
   Console.WriteLine("File is not in use");

Надеюсь, это поможет!

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

private async Task<Stream> GetStreamAsync()
{
    try
    {
        return new FileStream("sample.mp3", FileMode.Open, FileAccess.Write);
    }
    catch (IOException)
    {
        await Task.Delay(TimeSpan.FromSeconds(1));
        return await GetStreamAsync();
    }
}

Вы можете использовать этот поток как обычно:

using (var stream = await FileStreamGetter.GetStreamAsync())
{
    Console.WriteLine(stream.Length);
}

Принятые ответы выше страдают от проблемы, при которой, если файл был открыт для записи с помощью файлового ресурса.Режим чтения или если файл имеет атрибут "Только для чтения", код не будет работать.Это модифицированное решение работает наиболее надежно, при этом следует иметь в виду две вещи (что справедливо и для принятого решения).:

  1. Это не будет работать для файлов, которые были открыты в режиме общего доступа для записи
  2. Это не учитывает проблемы с потоками, поэтому вам нужно будет заблокировать его или обработать проблемы с потоками отдельно.

Имея в виду вышеизложенное, это проверяет, является ли файл либо заблокирован для записи или заблокирован для предотвращения чтения:

public static bool FileLocked(string FileName)
{
    FileStream fs = null;

    try
    {
        // NOTE: This doesn't handle situations where file is opened for writing by another process but put into write shared mode, it will not throw an exception and won't show it as write locked
        fs = File.Open(FileName, FileMode.Open, FileAccess.ReadWrite, FileShare.None); // If we can't open file for reading and writing then it's locked by another process for writing
    }
    catch (UnauthorizedAccessException) // https://msdn.microsoft.com/en-us/library/y973b725(v=vs.110).aspx
    {
        // This is because the file is Read-Only and we tried to open in ReadWrite mode, now try to open in Read only mode
        try
        {
            fs = File.Open(FileName, FileMode.Open, FileAccess.Read, FileShare.None);
        }
        catch (Exception)
        {
            return true; // This file has been locked, we can't even open it to read
        }
    }
    catch (Exception)
    {
        return true; // This file has been locked
    }
    finally
    {
        if (fs != null)
            fs.Close();
    }
    return false;
}

Помимо рабочих 3-х вкладышей и просто для справки:Если вы хотите, чтобы в полном разгаре информация - есть небольшой проект в Microsoft Dev Center:

https://code.msdn.microsoft.com/windowsapps/How-to-know-the-process-704839f4

Из введения:

Пример кода C #, разработанный в .NET Framework 4.0, поможет в выяснить, какой процесс заблокировал файл. RmStartSession функция, которая включена в rstrtmgr.библиотека dll была использована для создания сеанса restart manager и в соответствии с возвращаемым результатом создается новый экземпляр объекта Win32Exception.После регистрации ресурсов в сеансе Restart Manager с помощью RMREGISTERR Ресурсы функция, Список RmGetList функция вызывается, чтобы проверить каковы приложения, которые используют конкретный файл на перечисление в RM_PROCESS_INFO массив.

Это работает при подключении к "Сеансу Restart Manager".

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

Это может быть немного чрезмерно инженерный для ваших конкретных нужд...Но если это то, что ты хотите, идите вперед и беритесь за vs-project.

Вот некоторый код, который, насколько я могу судить, делает то же самое, что и принятый ответ, но с меньшим количеством кода:

    public static bool IsFileLocked(string file)
    {
        try
        {
            using (var stream = File.OpenRead(file))
                return false;
        }
        catch (IOException)
        {
            return true;
        }        
    }

Однако я думаю, что более надежно сделать это следующим образом:

    public static void TryToDoWithFileStream(string file, Action<FileStream> action, 
        int count, int msecTimeOut)
    {
        FileStream stream = null;
        for (var i = 0; i < count; ++i)
        {
            try
            {
                stream = File.OpenRead(file);
                break;
            }
            catch (IOException)
            {
                Thread.Sleep(msecTimeOut);
            }
        }
        action(stream);
    }

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

Вы можете установить его из nuget:Установка-пакет Xabe.Блокировка файлов

Если вы хотите получить дополнительную информацию об этом, проверьте https://github.com/tomaszzmuda/Xabe.Блокировка файлов

ILock fileLock = new FileLock(file);
if(fileLock.Acquire(TimeSpan.FromSeconds(15), true))
{
    using(fileLock)
    {
        // file operations here
    }
}

Блокировка файла.Метод Acquire вернет значение true, только если может заблокировать файл исключительно для этого объекта.Но приложение, которое загружает файл, тоже должно делать это в режиме блокировки файла.Если объект недоступен, метод возвращает false.

По моему опыту, вы обычно хотите сделать это, затем "защитить" свои файлы, чтобы сделать что-то необычное, а затем использовать "защищенные" файлы.Если у вас есть только один файл, который вы хотите использовать подобным образом, вы можете воспользоваться трюком, описанным в ответе Джереми Томпсона.Однако, если вы попытаетесь сделать это с большим количеством файлов (скажем, например, при написании программы установки), вы столкнетесь с серьезными проблемами.

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

Обратите внимание, что вы должны быть осведомлены об очевидных способах, которыми это можно использовать.В конце концов, файлы не будут заблокированы.Кроме того, имейте в виду, что существуют и другие причины, которые могут привести к вашему Move операция завершится неудачей.Очевидно, что здесь может помочь правильная обработка ошибок (MSDN).

var originalFolder = @"c:\myHugeCollectionOfFiles"; // your folder name here
var someFolder = Path.Combine(originalFolder, "..", Guid.NewGuid().ToString("N"));

try
{
    Directory.Move(originalFolder, someFolder);

    // Use files
}
catch // TODO: proper exception handling
{
    // Inform user, take action
}
finally
{
    Directory.Move(someFolder, originalFolder);
}

Для отдельных файлов я бы придерживался предложения по блокировке, опубликованного Джереми Томпсоном.

Мне интересно посмотреть, вызывает ли это какие-либо рефлексы WTF.У меня есть процесс, который создает и впоследствии запускает PDF-документ из консольного приложения.Однако я имел дело со слабостью, из-за которой, если бы пользователь запускал процесс несколько раз, генерируя один и тот же файл без предварительного закрытия ранее сгенерированного файла, приложение выдало бы исключение и умерло.Это было довольно частое явление, поскольку имена файлов основаны на номерах предложений по продажам.

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

private static string WriteFileToDisk(byte[] data, string fileName, int version = 0)
{
    try
    {
        var versionExtension = version > 0 ? $"_{version:000}" : string.Empty;
        var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"{fileName}{versionExtension}.pdf");
        using (var writer = new FileStream(filePath, FileMode.Create))
        {
            writer.Write(data, 0, data.Length);
        }
        return filePath;
    }
    catch (IOException)
    {
        return WriteFileToDisk(data, fileName, ++version);
    }
}

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

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

Поможет ли что-то подобное этому?

var fileWasWrittenSuccessfully = false;
while (fileWasWrittenSuccessfully == false)
{
    try
    {
        lock (new Object())
        {
            using (StreamWriter streamWriter = new StreamWriter(filepath.txt"), true))
            {
                streamWriter.WriteLine("text");
            }
        }

        fileWasWrittenSuccessfully = true;
    }
    catch (Exception)
    {

    }
}

Однажды мне понадобилось загрузить PDF-файлы в онлайн-архив резервных копий.Но резервное копирование завершилось бы неудачей, если бы пользователь открыл файл в другой программе (например, PDF reader).В спешке я попробовал несколько лучших ответов в этой теме, но не смог заставить их работать.Что действительно сработало для меня, так это попытка переместить PDF-файл в свой собственный каталог.Я обнаружил, что это не сработало бы, если бы файл был открыт в другой программе, и если бы перемещение прошло успешно, не потребовалось бы операции восстановления, как это было бы, если бы он был перемещен в отдельный каталог.Я хочу опубликовать свое базовое решение на случай, если оно может быть полезно для конкретных случаев использования другими.

string str_path_and_name = str_path + '\\' + str_filename;
FileInfo fInfo = new FileInfo(str_path_and_name);
bool open_elsewhere = false;
try
{
    fInfo.MoveTo(str_path_and_name);
}
catch (Exception ex)
{
    open_elsewhere = true;
}

if (open_elsewhere)
{
    //handle case
}

Попробуйте переместить / скопировать файл во временный каталог.Если вы можете, у него нет блокировки, и вы можете безопасно работать во временном каталоге, не получая блокировок.В противном случае просто попробуйте переместить его еще раз через x секунд.

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

Итак, я добавил дополнительный код для этого.В моем случае я хочу загрузить XDocument:

        XDocument xDoc = null;

        while (xDoc == null)
        {
            while (IsFileBeingUsed(_interactionXMLPath))
            {
                Logger.WriteMessage(Logger.LogPrioritet.Warning, "Deserialize can not open XML file. is being used by another process. wait...");
                Thread.Sleep(100);
            }
            try
            {
                xDoc = XDocument.Load(_interactionXMLPath);
            }
            catch
            {
                Logger.WriteMessage(Logger.LogPrioritet.Error, "Load working!!!!!");
            }
        }

А ты как думаешь?Могу ли я что-то изменить?Может быть, мне вообще не нужно было использовать функцию IsFileBeingUsed?

Спасибо

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top