IOException ausgelöst trotz IOException catch block

Wir haben eine Windows Forms-Anwendung, die eine Verbindung zu einigen Webdiensten herstellt. Es listet die Dokumente im System auf. Wenn der Benutzer auf eines doppelklickt, lädt er die Datei auf den lokalen Computer herunter und öffnet das Dokument, damit sie bearbeitet werden können. Sobald der Benutzer das Dokument geschlossen hat, laden wir es zurück in das System.

Für diesen Vorgang haben wir die Dateisperre für das Dokument überwacht. Sobald die Dateisperre aufgehoben ist, laden wir das Dokument hoch.

DasIsFileLocked Methode sieht so aus:

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

Wir nennen dies eine Schleife mit einem 5 Sekunden langen Ruhezustand zwischen den Versuchen. Dies scheint die meiste Zeit großartig zu funktionieren, aber gelegentlich sehen wir einIOException von dieser Methode. Ich kann nicht sehen, wie diese Ausnahme ausgelöst werden kann.

Die Ausnahme ist:

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()
...

Der andere ungerade Teil ist der Stack-Trace. Dies bezieht sich aufGetOutlook ist ein völlig anderer Teil des Systems (unabhängig von der Dokumentenverarbeitung). Es gibt zwei Codepfade inIsFileLocked und auch nicht über das @ erreichbGetOutlookInternal Methode. Es ist fast so, als würde der Stack beschädigt.

Warum kein @ verwend FileSystemWatcher?

Als Randnotiz haben wir erwogen, ein @ zu verwendeFileSystemWatcher, um Dateiänderungen zu überwachen, hat diesen Ansatz jedoch nicht berücksichtigt, da der Benutzer das Dokument möglicherweise geöffnet lässt und weitere Änderungen daran vornimmt. Unsere Webservices entsperren das Dokument, sobald wir es hochladen, sodass wir dies erst tun können, wenn der Benutzer damit fertig ist.

Wir beschäftigen uns nur mit Dokumenten, die durch ihre Anwendung gesperrt sind. Ich schätze, dass es einige Anwendungen gibt, die ihre Dateien nicht sperren, aber wir müssen sie hier nicht berücksichtigen.

Die Outlook-Methoden

Below ist dasGetOutlookInternal -Methode, die im Stapel angezeigt wird - wie Sie sehen, handelt es sich nur um Outlook Interop und hat nichts mit dem Öffnen des Dokuments zu tun. Es ruft nicht inIsFileLocked:

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

Antworten auf die Frage(2)

Ihre Antwort auf die Frage