use - check if file is blocked c#




Existe uma maneira de verificar se um arquivo está em uso? (11)

Eu estou escrevendo um programa em c # que precisa acessar repetidamente 1 arquivo de imagem. Na maior parte do tempo ele funciona, mas se meu computador estiver rodando rápido, ele tentará acessar o arquivo antes de ser salvo de volta ao sistema de arquivos e lançar um erro: "Arquivo em uso por outro processo" .

Eu gostaria de encontrar uma maneira de contornar isso, mas todo o meu Googling apenas produziu verificações usando o tratamento de exceções. Isso é contra a minha religião, então eu queria saber se alguém tem uma maneira melhor de fazer isso?


Além de trabalhar 3-liners e apenas para referência: Se você quiser a informação completa - há um pequeno projeto no Microsoft Dev Center:

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

Da Introdução:

O código de exemplo C # desenvolvido no .NET Framework 4.0 ajudaria a descobrir qual é o processo que está tendo um bloqueio em um arquivo. A função RmStartSession incluída no rstrtmgr.dll foi usada para criar uma sessão do gerenciador de reinicialização e, de acordo com o resultado de retorno, uma nova instância do objeto Win32Exception é criada. Depois de registrar os recursos em uma sessão do Restart Manager por meio da função RmRegisterRescources , a função RmGetList é chamada para verificar quais são os aplicativos que estão usando um determinado arquivo, enumerando a matriz RM_PROCESS_INFO .

Funciona conectando-se à "Sessão do Reiniciar Manager".

O Gerenciador de Reinicialização usa a lista de recursos registrados na sessão para determinar quais aplicativos e serviços devem ser desligados e reiniciados. Os recursos podem ser identificados por nomes de arquivos, nomes abreviados de serviço ou estruturas RM_UNIQUE_PROCESS que descrevem aplicativos em execução.

Pode ser um pouco overengineered para suas necessidades particulares ... Mas se é isso que você quer, vá em frente e pegue o projeto vs.


Apenas use a exceção como pretendido. Aceite que o arquivo está em uso e tente novamente, repetidamente, até que sua ação seja concluída. Este também é o mais eficiente porque você não perde nenhum ciclo verificando o estado antes de agir.

Use a função abaixo, por exemplo

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

Método reutilizável que expira após 2 segundos

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

As respostas aceitas acima sofreram um problema em que se o arquivo foi aberto para gravação com um modo FileShare.Read ou se o arquivo tiver um atributo Read-Only, o código não funcionará. Essa solução modificada funciona da maneira mais confiável, com duas coisas em mente (como também é verdade para a solução aceita):

  1. Não funcionará para arquivos que foram abertos com um modo de compartilhamento de gravação
  2. Isso não leva em consideração problemas de encadeamento, portanto, você precisará bloqueá-lo ou lidar com problemas de encadeamento separadamente.

Tendo isso em mente, isso verifica se o arquivo está bloqueado para gravação ou bloqueado para impedir a leitura :

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

Estou interessado em ver se isso desencadeia algum reflexo WTF. Eu tenho um processo que cria e subseqüentemente lança um documento PDF de um aplicativo de console. No entanto, eu estava lidando com uma fragilidade onde se o usuário fosse executar o processo várias vezes, gerando o mesmo arquivo sem antes fechar o arquivo gerado anteriormente, o aplicativo lançaria uma exceção e morreria. Essa foi uma ocorrência bastante freqüente porque os nomes de arquivos são baseados nos números de cotação de vendas.

Em vez de falhar de uma maneira tão deselegante, decidi confiar no versionamento de arquivos com incrementos automáticos:

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

Provavelmente, um pouco mais de cuidado pode ser dado ao bloco catch para garantir que estou capturando as IOException (s) corretas. Provavelmente, também limparei o armazenamento do aplicativo na inicialização, uma vez que esses arquivos devem ser temporários de qualquer maneira.

Eu percebo que isso vai além do escopo da questão do OP de simplesmente verificar se o arquivo está em uso, mas este era realmente o problema que eu estava procurando resolver quando cheguei aqui, então talvez seja útil para outra pessoa.


Na minha experiência, você geralmente quer fazer isso, então 'proteja' seus arquivos para fazer algo extravagante e então use os arquivos 'protegidos'. Se você tem apenas um arquivo que você quer usar como este, você pode usar o truque que é explicado na resposta de Jeremy Thompson. No entanto, se você tentar fazer isso em vários arquivos (por exemplo, quando estiver escrevendo um instalador), você terá um pouco de dor.

Uma maneira muito elegante de resolver isso é usar o fato de seu sistema de arquivos não permitir que você altere um nome de pasta se um dos arquivos nele estiver sendo usado. Mantenha a pasta no mesmo sistema de arquivos e funcionará como um encanto.

Note que você deve estar ciente das maneiras óbvias de como isso pode ser explorado. Afinal, os arquivos não serão bloqueados. Além disso, esteja ciente de que há outros motivos que podem resultar na falha da operação do Move . Obviamente, o tratamento adequado de erros (MSDN) pode ajudar aqui.

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

Para arquivos individuais eu ficaria com a sugestão de bloqueio postada por Jeremy Thompson.


Talvez você possa usar um FileSystemWatcher e observar o evento Changed.

Eu não usei isso sozinho, mas pode valer a pena. Se o filesystemwatcher for um pouco pesado para este caso, eu usaria o loop try / catch / sleep.


Use isto para verificar se um arquivo está bloqueado:

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

Por motivos de desempenho, recomendo que você leia o conteúdo do arquivo na mesma operação. aqui estão alguns exemplos:

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

Experimente você mesmo:

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

Você pode retornar uma tarefa que lhe fornece um fluxo assim que estiver disponível. É uma solução simplificada, mas é um bom ponto de partida. É thread seguro.

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

Você pode usar este fluxo como de costume:

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

Você pode usar minha biblioteca para acessar arquivos de vários aplicativos.

Você pode instalá-lo a partir do nuget: Install-Package Xabe.FileLock

Se você quiser mais informações sobre isso, verifique https://github.com/tomaszzmuda/Xabe.FileLock

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

O método fileLock.Acquire retornará true somente se puder bloquear o arquivo exclusivo para este objeto. Mas app que upload de arquivo deve fazê-lo no bloqueio de arquivos também. Se o objeto estiver inacessível, o método retorna false.


a única maneira que conheço é usar a API de bloqueio exclusiva do Win32, que não é muito rápida, mas existem exemplos.

A maioria das pessoas, por uma solução simples para isso, simplesmente tenta / pega / dorme loops.


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

Espero que isto ajude!





file-locking