Pergunta

Eu estou escrevendo um programa em C # que precisa repetidamente arquivo de imagem de acesso 1. Na maioria das vezes ele funciona, mas se o meu computador está correndo rápido, ele vai tentar acessar o arquivo antes que ele foi salvo de volta para o sistema de arquivos e lançar um erro: "Arquivo em uso por outro processo" .

Eu gostaria de encontrar uma maneira de contornar isso, mas toda a minha pesquisando só cedeu a criação de cheques usando manipulação de exceção. Isso é contra minha religião, então eu queria saber se alguém tem uma maneira melhor de fazê-lo?

Foi útil?

Solução

Atualizado NOTA sobre esta solução : Verificar com FileAccess.ReadWrite falhará para arquivos somente leitura para que a solução foi modificado para verificar com FileAccess.Read. Enquanto esta solução funciona porque tentando verificar com FileAccess.Read falhará se o arquivo tem um Write ou bloqueio de leitura sobre ele, no entanto, esta solução não vai funcionar se o arquivo não tem um escrever ou ler bloqueio sobre ele, ou seja, ele tem sido aberta (para leitura ou escrita) com FileShare.Read ou FileShare.Write acesso.

ORIGINAL: Eu usei esse código para os últimos anos, e eu não tive qualquer problema com ele.

Entenda sua hesitação sobre o uso de exceções, mas você não pode evitá-los o tempo todo:

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

Outras dicas

Você pode sofrer de uma condição segmento de corrida neste quais não são documentados exemplos de este ser usado como uma vulnerabilidade de segurança. Se você verificar que o arquivo está disponível, mas, em seguida, tentar usá-lo você poderia jogar naquele ponto, que um utilizador mal intencionado poderia usar a força e explorar em seu código.

Sua melhor aposta é um try catch / finally que tenta obter o identificador de arquivo.

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

Use este 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, eu 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");

Talvez você poderia usar um FileSystemWatcher e relógio para o evento Changed.

Eu não usei isso mesmo, mas pode valer a pena um tiro. Se o FileSystemWatcher acaba por ser um pouco pesado para este caso, eu iria para o / catch / loop tentativa sono.

Basta usar a exceção como pretendido. Aceitar 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 perca nenhum ciclos verificar 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);
}

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

A maioria das pessoas, para uma solução simples para isso, simplesmente para try / catch / loops de sono.

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 isso ajude!

Você pode retornar uma tarefa que lhe dá um fluxo tão logo ele se torna disponível. É uma solução simplificada, mas é um bom ponto de partida. É de thread-safe.

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

As respostas aceitas acima sofrer um problema onde se o arquivo foi aberto para escrever com um modo FileShare.Read ou se o arquivo tem um atributo Somente leitura do código não vai funcionar. Esta solução modificada funciona de forma mais confiável, com duas coisas a ter em mente (como verdade para a solução aceita também):

  1. Não vai funcionar para arquivos que foi aberto com um modo de escrita share
  2. Este não leva em conta questões de segmentação para que você vai precisar para bloqueá-lo para baixo ou alça enfiar questões separadamente.

Mantendo o acima em mente, este verifica se o arquivo é bloqueado para escrever ou bloqueada 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;
}

Além de trabalhar 3-liners e apenas para referência: Se você deseja que o plena explodido informações - há um pequeno projeto em Microsoft Dev Center:

https://code.msdn.microsoft .com / windowsapps / How-to-know-do-processo-704839f4

A partir da introdução:

O código de exemplo C # desenvolvido em .NET Framework 4.0 ajudaria na descobrir qual é o processo que está a ter um bloqueio em um arquivo. RmStartSession função que está incluído no rstrtmgr.dll tem sido usado para criar uma sessão de reinício gerente e de acordo com o retorno resultar uma nova instância do Win32Exception objeto é criado. Depois de registrar os recursos para uma sessão Restart Manager via RmRegisterRescources função, RmGetList função é invocado para verificação Quais são as aplicações estão usando um arquivo específico, enumerando RM_PROCESS_INFO array.

Ele funciona através da conexão com a "Sessão Restart Manager".

O Restart Manager usa a lista de recursos registrada na sessão de determinar quais aplicativos e serviços devem ser desligado e reiniciado. Os recursos podem ser identificados por nomes de arquivos, nomes curtos de serviços, ou estruturas RM_UNIQUE_PROCESS que descrevem os 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.

Aqui está um código que, tanto quanto eu posso dizer melhor faz a mesma coisa que a resposta aceita, mas com menos código:

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

No entanto, penso que é mais robusto para fazê-lo da seguinte maneira:

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

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

Você pode instalá-lo do NuGet: Install-Package Xabe.FileLock

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

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

método fileLock.Acquire retornará verdadeiro somente se o arquivo pode bloqueio exclusivo para este objeto. Mas aplicativo qual arquivo de upload deve fazê-lo no bloqueio de arquivo também. Se o objeto é inacessíveis Metod retorna falso.

Na minha experiência, você normalmente quer fazer isso, então 'proteger' seus arquivos para fazer algo extravagante e, em seguida, usar os arquivos 'protegidos'. Se você tiver apenas um arquivo que você deseja usar como este, você pode usar o truque que é explicado na resposta por Jeremy Thompson. No entanto, se você tentar fazer isso em lotes de arquivos (digamos, por exemplo, quando você está escrevendo um instalador), você está em um pouco de dor.

Uma maneira muito elegante isso pode ser resolvido é usando o fato de que seu sistema de arquivos não permitirá que você altere o nome da pasta, se um dos arquivos não está sendo usado. Mantenha a pasta no mesmo sistema de arquivo e ele vai funcionar como um encanto.

Note que você deve estar ciente das maneiras óbvias Isto pode ser explorado. Afinal, os arquivos não serão bloqueadas. Além disso, estar ciente de que existem outras razões que podem resultar em sua operação Move a falhar. Obviamente manipulação de erro apropriada (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 que eu ia ficar com a sugestão de bloqueio postado por Jeremy Thompson.

Estou interessado para ver se isso provoca quaisquer reflexos WTF. Eu tenho um processo que cria e posteriormente lança um documento PDF a partir de um aplicativo console. No entanto, eu estava lidando com uma fragilidade em que se o usuário fosse para executar o processo várias vezes, gerando o mesmo arquivo sem primeiro fechar o arquivo gerado anteriormente, o aplicativo seria lançar uma exceção e morrer. Esta foi uma ocorrência bastante freqüente porque os nomes de arquivo são baseados em vendas citar números.

Ao invés de falhar de uma maneira tão deselegante, decidi confiar no arquivo auto-incrementada versões:

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 para o bloco catch para garantir que eu estou pegando o IOException correta (s). Eu provavelmente também limpar o armazenamento de aplicativo na inicialização uma vez que esses arquivos são destinados a ser temporário de qualquer maneira.

Sei que isso vai além do âmbito da pergunta do OP de simplesmente verificar se o arquivo está em uso, mas este foi realmente o problema que eu estava olhando para resolver quando cheguei aqui, então talvez seja útil para alguém.

Será que algo como isso ajuda?

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

    }
}

Uma vez eu precisava fazer upload de PDFs para um arquivo de backup online. Mas o backup falhará se o usuário tivesse o arquivo aberto em outro programa (como o leitor de PDF). Na minha pressa, eu tentei algumas das principais respostas neste segmento, mas não conseguiu levá-los para o trabalho. O que fez o trabalho para mim estava tentando mover o arquivo PDF para seu próprio diretório . Achei que este seria um fracasso se o arquivo foi aberto em outro programa, e se o movimento fosse bem sucedida, não haveria restauração-operação necessária como haveria se fosse movido para um diretório separado. Quero postar minha solução básica no caso, pode ser útil para casos de uso específico dos outros.

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
}

Tente e mover / copiar o arquivo para a dir temporário. Se você puder, não tem fechadura e você pode seguramente trabalhar no dir temporário sem obter bloqueios. Else apenas tentar movê-lo novamente em x segundos.

Eu uso essa solução alternativa, mas eu tenho um período de tempo entre quando eu verificar o bloqueio de arquivos com a função IsFileLocked e quando eu abrir o arquivo. Neste período de tempo algum outro thread pode abrir o arquivo, por isso vou ficar IOException.

Então, eu adicionei código extra para isso. No meu caso eu quero XDocument carga:

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

O que você acha? eu posso mudar alguma coisa? Talvez eu não tivesse que usar a função IsFileBeingUsed em tudo?

Graças

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top