IOException aumentada apesar do bloco catch IOException

Temos um aplicativo Windows Forms que se conecta a alguns serviços da web. Ele lista os documentos no sistema e, quando o usuário clica duas vezes em um, fazemos o download do arquivo no computador local e abrimos o documento para edição. Depois que o usuário fecha o documento, fazemos o upload novamente para o sistema.

Para esse processo, monitoramos o bloqueio de arquivo no documento. Assim que o bloqueio do arquivo é liberado, carregamos o documento.

oIsFileLocked O método se parece com isso:

private const int ErrorLockViolation = 33;
private const int ErrorSharingViolation = 32;

private static bool IsFileLocked(string fileName)
{
    Debug.Assert(!string.IsNullOrEmpty(fileName));

    try
    {
        if (File.Exists(fileName))
        {
            using (FileStream fs = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.None))
            {
                fs.ReadByte();
            }
        }

        return false;
    }
    catch (IOException ex)
    {
        // get the HRESULT for this exception
        int errorCode = Marshal.GetHRForException(ex) & 0xFFFF;

        return errorCode == ErrorSharingViolation || errorCode == ErrorLockViolation;
    }
}

Chamamos isso de um loop com um sono de 5 segundos entre as tentativas. Isso parece funcionar muito bem na maioria das vezes, mas ocasionalmente vemos umaIOException deste método. Não vejo como é possível lançar essa exceção.

A exceção é:

IOException: The process cannot access the file 'C:\Users\redacted\AppData\Roaming\redacted\Jobs\09c39a4c-c1a3-4bb9-a5b5-54e00bb6c747\4b5c4642-8ede-4881-8fa9-a7944852d93e\CV abcde abcdef.docx' because it is being used by another process.
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, String msgPath, Boolean bFromProxy)
at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share)
at redacted.Helpers.IsFileLocked(String fileName)
at System.Runtime.InteropServices.Marshal.GetActiveObject(Guid& rclsid, IntPtr reserved, Object& ppunk)
at System.Runtime.InteropServices.Marshal.GetActiveObject(String progID)
at redacted.OutlookHelper.GetOutlookInternal()
at redacted.OutlookHelper.GetOutlook()
...

A outra parte ímpar é o rastreamento da pilha. Isso se refere aGetOutlook que é totalmente uma parte diferente do sistema (não relacionada ao manuseio de documentos). Existem dois caminhos de código emIsFileLocked e nem são alcançáveis através doGetOutlookInternal método. É quase como se a pilha estivesse ficando corrompida.

Por que não usar umFileSystemWatcher?

Como observação, consideramos o uso de umFileSystemWatcher para monitorar alterações no arquivo, mas desconsidera essa abordagem, pois o usuário pode manter o documento aberto e continuar fazendo outras alterações. Nossos serviços da Web desbloqueiam o documento assim que o carregamos, para que não possamos fazer isso até que o usuário tenha concluído o processo.

Estamos preocupados apenas com documentos bloqueados pelo aplicativo. Compreendo que existem alguns aplicativos que não bloqueiam seus arquivos, mas não precisamos considerá-los aqui.

Os métodos do Outlook

Abaixo está oGetOutlookInternal método que aparece na pilha - como você pode ver, está lidando apenas com a Interoperabilidade do Outlook e não está relacionado à abertura do documento. Não chama emIsFileLocked:

    private static Application GetOutlookInternal()
    {
        Application outlook;

        // Check whether there is an Outlook process running.
        if (Process.GetProcessesByName("OUTLOOK").Length > 0)
        {
            try
            {
                // If so, use the GetActiveObject method to obtain the process and cast it to an Application object.
                outlook = (Application)Marshal.GetActiveObject("Outlook.Application");
            }
            catch (COMException ex)
            {
                if (ex.ErrorCode == -2147221021)    // HRESULT: 0x800401E3 (MK_E_UNAVAILABLE)
                {
                    // Outlook is running but not ready (not in Running Object Table (ROT) - http://support.microsoft.com/kb/238610)
                    outlook = CreateOutlookSingleton();
                }
                else
                {
                    throw;
                }
            }
        }
        else
        {
            // If not running, create a new instance of Outlook and log on to the default profile.
            outlook = CreateOutlookSingleton();
        }
        return outlook;
    }

    private static Application CreateOutlookSingleton()
    {
        Application outlook = new Application();

        NameSpace nameSpace = null;
        Folder folder = null;
        try
        {
            nameSpace = outlook.GetNamespace("MAPI");

            // Create an instance of the Inbox folder. If Outlook is not already running, this has the side
            // effect of initializing MAPI. This is the approach recommended in http://msdn.microsoft.com/en-us/library/office/ff861594(v=office.15).aspx
            folder = (Folder)nameSpace.GetDefaultFolder(OlDefaultFolders.olFolderInbox);
        }
        finally
        {
            Helpers.ReleaseComObject(ref folder);
            Helpers.ReleaseComObject(ref nameSpace);
        }

        return outlook;
    }

questionAnswers(1)

yourAnswerToTheQuestion