We have a Windows Forms application that connects to some web services. It lists the documents in the system, and when the user double-clicks one, we upload the file to the local computer and open the document for editing. As soon as the user closes the document, we upload it back to the system.
For this process, we track the file lock in the document. Once the file lock is released, we will upload the document.
The IsFileLocked method is as follows:
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) {
We call this in a 5 second sleep cycle between attempts. This seems to work most of the time, but sometimes we see an IOException from this method. I do not see how this can be ruled out.
The exception is:
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() ...
The other odd part is the stack trace. This applies to GetOutlook , which is completely different from the system (not related to document processing). There are two code paths in IsFileLocked , and none of them are accessible through the GetOutlookInternal method. It looks like the stack is corrupted.
Why not use FileSystemWatcher ?
As a note, we considered using FileSystemWatcher to monitor file changes, but abandoned this approach because the user can leave the document open and continue making further changes to it. Our web services will unlock the document as soon as we download it, so we wonβt be able to do this until the user finishes working with it.
We deal only with documents that are blocked by their application. I appreciate that there are some applications that do not block their files, but we do not need to consider them here.
Outlook Methods
Below is the GetOutlookInternal method, which appears on the stack - as you can see, it works only with Outlook Interop and is not associated with opening a document. It does not call 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; }