Como saber se uma ioexception capturada é causada pelo arquivo usado por outro processo, sem recorrer a analisar a propriedade de mensagem da exceção

StackOverflow https://stackoverflow.com/questions/2568875

Pergunta

Quando abro um arquivo, quero saber se ele está sendo usado por outro processo para que eu possa executar um manuseio especial; Qualquer outra ioexception eu irei borbulhar. A propriedade de mensagem de uma ioexception contém "o processo não pode acessar o arquivo 'foo' porque está sendo usado por outro processo.", Mas isso é inadequado para detecção programática. Qual é a maneira mais segura e mais robusta de detectar um arquivo usado por outro processo?

Foi útil?

Solução

Esta versão em particular de IOException é jogado quando o código de erro retornado da função nativa Win32 é ERROR_SHARING_VIOLATION (Documentação). Tem o valor numérico de 0x20 mas é realmente armazenado como 0x80070020 no HRESULT propriedade da exceção (é o resultado de chamar makehrfromerrorCode).

Portanto, a maneira programática de verificar uma violação de compartilhamento é verificar o HResult propriedade no IOException para o valor 0x80070020.

public static bool IsSharingViolation(this IOException ex) {
  return 0x80070020 == Marshal.GetHRForException(ex);
}

No entanto, questiono o que exatamente você deseja fazer no cenário que foi jogado como resultado de uma violação de compartilhamento. No momento em que a exceção é lançada, o outro processo pode sair e, portanto, remover a violação.

Outras dicas

Eu não tenho "representante" suficiente para comentar, então espero que esta "resposta" esteja bem ...

A resposta aceita é exatamente o que eu estava procurando e funciona perfeitamente, mas as pessoas aqui e em perguntas semelhantes questionaram a utilidade de verificar se um arquivo estiver bloqueado. É correto que uma função de utilidade para testar se um arquivo for bloqueado não for muito uso, porque na próxima instrução, o status poderia ter alterado.

Mas o padrão de tentar uma operação de bloqueio e depois responder de maneira diferente para travar erros versus erros gerais é válida e útil. A coisa mais óbvia a fazer é esperar um pouco e tentar novamente a operação. Isso pode ser generalizado em uma função auxiliar como esta:

protected static void RetryLock(Action work) {
    // Retry LOCK_MAX_RETRIES times if file is locked by another process
    for (int i = 1; i <= LOCK_MAX_RETRIES; i++) {
        try {
            work();
            return;
        } catch (IOException ex) {
            if (i == LOCK_MAX_RETRIES || (uint) ex.HResult != 0x80070020) {
                throw;
            } else {
                // Min should be long enough to generally allow other process to finish
                // while max should be short enough such that RETRIES * MAX isn't intolerable
                Misc.SleepRandom(LOCK_MIN_SLEEP_MS, LOCK_MAX_SLEEP_MS);
            }
        }
    }
} // RetryLock

... que pode ser usado assim:

public string DoSomething() {
    string strReturn = null;
    string strPath = @"C:\Some\File\Path.txt";
    // Do some initial work...

    //----------------------------------------------------------------------------------------------------
    // NESTED FUNCTION to do main logic, RetryLock will retry up to N times on lock failures
    Action doWork = delegate {
        using (FileStream objFile = File.Open(strPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)) {
            // Does work here if lock succeeded, else File.Open will throw ex
            strReturn = new StreamReader(objFile).ReadLine();
        }
    }; // delegate doWork
    //----------------------------------------------------------------------------------------------------

    RetryLock(doWork); // Throws original ex if non-locking related or tried max times
    return strReturn;
}

... De qualquer forma, apenas postar caso alguém com necessidades semelhantes ache o padrão útil.

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