尽管存在IOException catch块,仍会引发IOException

我们有一个Windows窗体应用程序连接到一些Web服务。 它列出了系统中的文档,当用户双击一个文档时,我们将文件下载到本地计算机并打开文档供他们编辑。 一旦用户关闭文档,我们就将其上传回系统。

对于此过程,我们一直在监视文档上的文件锁定。 一旦文件锁被释放,我们就上传文档。

IsFileLocked方法如下所示:

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

我们称之为循环,尝试之间有5秒的睡眠。 这似乎在大多数时候都很好用,但偶尔我们会看到这个方法的IOException 。 我无法看到如何抛出此exception。

例外是:

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

另一个奇怪的部分是堆栈跟踪。 这指的是GetOutlook ,它完全是系统的不同部分(与文档处理无关)。 IsFileLocked有两个代码路径,并且都不能通过GetOutlookInternal方法访问。 这几乎就像堆栈正在腐败一样。

为什么不使用FileSystemWatcher ?

作为旁注,我们确实考虑使用FileSystemWatcher来监视文件更改,但是对此方法打了折扣,因为用户可以保持文档打开并继续对其进行进一步更改。 我们的网络服务在我们上传后立即解锁文档,因此在用户完成文档之前我们无法执行此操作。

我们只关注其应用程序锁定的文档。 我感谢有些应用程序没有锁定他们的文件,但我们不需要在这里考虑它们。

Outlook方法

下面是堆栈中出现的GetOutlookInternal方法 – 正如您所看到的,它只处理Outlook Interop并且与文档打开无关。 它不会调用IsFileLocked

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

我不小心偶然发现了这篇文章,这篇文章有助于找到我的问题的原因: Marshal.GetHRForException不仅仅是Get-HR-For-Exception

事实certificate我们有两个线程,一个是在IOException上调用Marshal.GetHRForException(...)来确定文件是否被锁定(Win32错误代码32或33)。 另一个线程是调用Marshal.GetActiveObject(...)来使用Interop连接到Outlook实例。

如果GetHRForException调用GetHRForException ,然后第二次调用GetActiveObject但抛出COMException ,则会得到完全错误的exception和堆栈跟踪。 这是因为GetHRForException有效地“设置”了exception,而GetActiveObject将抛出该exception,而不是真正的COMException

重现示例代码:

可以使用以下代码重现此问题。 创建一个新的控制台应用程序,导入Outlook COM引用,并粘贴代码。 启动应用程序时确保Outlook未运行:

  public static void Main(string[] args) { bool isLocked = IsFileLocked(); Console.WriteLine("IsLocked = " + isLocked); ShowOutlookWindow(); } private static bool IsFileLocked() { try { using (FileStream fs = File.Open(@"C:\path\to\non_existant_file.docx", FileMode.Open, FileAccess.Read, FileShare.None)) { fs.ReadByte(); return false; } } catch (IOException ex) { int errorCode = Marshal.GetHRForException(ex) & 0xFFFF; return errorCode == 32 || errorCode == 33; // lock or sharing violation } } private static void ShowOutlookWindow() { try { Application outlook = (Application)Marshal.GetActiveObject("Outlook.Application"); // ^^ causes COMException because Outlook is not running MailItem mailItem = outlook.CreateItem(OlItemType.olMailItem); mailItem.Display(); } catch (System.Exception ex) { Console.WriteLine(ex); throw; } } 

您可能希望在控制台中看到COMException ,但这就是您所看到的

 IsLocked = False System.IO.DirectoryNotFoundException: Could not find a part of the path 'C:\path\to\non_existant_file.docx'. 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) at System.IO.File.Open(String path, FileMode mode, FileAccess access, FileShare share) at MyProject.Program.IsFileLocked() at System.Runtime.InteropServices.Marshal.GetActiveObject(Guid& rclsid, IntPtr reserved, Object& ppunk) at System.Runtime.InteropServices.Marshal.GetActiveObject(String progID) at MyProject.Program.ShowOutlookWindow() 

请注意exception是DirectoryNotFoundException ,并且堆栈错误地建议将GetActiveObject调用为IsFileLocked

解:

解决这个问题的方法只是使用Exception.HResult属性而不是GetHRForException 。 以前这个属性受到保护,但现在可以访问它,因为我们将项目升级到.NET 4.5

 private static bool IsFileLocked() { try { using (FileStream fs = File.Open(@"C:\path\to\non_existant_file.docx", FileMode.Open, FileAccess.Read, FileShare.None)) { fs.ReadByte(); return false; } } catch (IOException ex) { int errorCode = ex.HResult & 0xFFFF; return errorCode == 32 || errorCode == 33; // lock or sharing violation } } 

通过此更改,行为符合预期。 控制台现在显示:

 IsLocked = False System.Runtime.InteropServices.COMException (0x800401E3): Operation unavailable (Exception from HRESULT: 0x800401E3 (MK_E_UNAVAILABLE)) at System.Runtime.InteropServices.Marshal.GetActiveObject(Guid& rclsid, IntPtr reserved, Object& ppunk) at System.Runtime.InteropServices.Marshal.GetActiveObject(String progID) at MyProject.Program.ShowOutlookWindow() 

TL; DR:如果您还使用COM组件,请不要使用Marshal.GetHRForException