Есть ли способ проверить, используется ли файл?

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

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

16 ответов


Обновлено примечание по этому решению: проверки с FileAccess.ReadWrite будет не только для чтения, поэтому решение было изменено для проверки с FileAccess.Read. Хотя это решение работает, потому что пытается проверить с FileAccess.Read произойдет сбой, если файл имеет блокировку записи или чтения, однако это решение не будет работать, если файл не имеет блокировки записи или чтения, т. е. он был открыт (для чтения или записи) с помощью 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;
}

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

лучше всего попробовать поймать / наконец, который пытается получить дескриптор файла.

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 и следите за измененным событием.

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


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

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


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

надеюсь, что это помогает!


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

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

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

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

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

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

    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: Install-Package Xabe.Забыл

Если вы хотите больше информации о нем, проверьте https://github.com/tomaszzmuda/Xabe.FileLock

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

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


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

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

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

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


помимо рабочих 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 через RmRegisterRescourcesвы хотите, вперед и захватить vs-проект.


мне интересно посмотреть, вызывает ли это какие-либо рефлексы 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 о простой проверке того, используется ли файл, но это действительно была проблема хотите решить когда я приехал сюда, так что, возможно, это будет полезно кому-то еще.


попробуйте переместить / скопировать файл в временный каталог. Если вы можете, у него нет замка, и вы можете безопасно работать в temp dir без блокировки. Остальное просто попробуйте переместить его снова в 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?

спасибо