Есть ли способ проверить, используется ли файл? - PullRequest
778 голосов
/ 18 мая 2009

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

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

Ответы [ 17 ]

534 голосов
/ 18 мая 2009

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

Ваша лучшая ставка - это попытка перехватить / наконец, которая пытается получить дескриптор файла.

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.
}
505 голосов
/ 02 июня 2009

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

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

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

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;
}
84 голосов
/ 16 июня 2012

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

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");
7 голосов
/ 18 мая 2009

Возможно, вы могли бы использовать FileSystemWatcher и наблюдать за измененным событием.

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

6 голосов
/ 26 мая 2015

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

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

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);
}
4 голосов
/ 20 октября 2015

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

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);
}
4 голосов
/ 22 августа 2011
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");

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

4 голосов
/ 18 мая 2009

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

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

3 голосов
/ 15 октября 2015

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

  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 голосов
/ 09 октября 2018

Помимо работы с 3-мя линиями и просто для справки: если вам нужна полная информация - в Microsoft Dev Center есть небольшой проект:

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

Из введения:

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

Работает, подключившись к «Сеану Restart Manager».

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

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

...