Como saber se uma ioexception capturada é causada pelo arquivo usado por outro processo, sem recorrer a analisar a propriedade de mensagem da exceção
-
24-09-2019 - |
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?
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.