У нас есть приложение Windows Forms, которое подключается к некоторым веб-службам. Он перечисляет документы в системе, и когда пользователь дважды щелкает один, мы загружаем файл на локальный компьютер и открываем документ для редактирования. Как только пользователь закрывает документ, мы загружаем его обратно в систему.Исключение IOException, несмотря на блокирование блокировки IOException
Для этого процесса мы отслеживаем блокировку файла в документе. Как только блокировка файла будет выпущена, мы загрузим документ.
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
по этому методу. Я не вижу, как это можно исключить.
Исключение:
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
для мониторинга изменений файлов, но уклонились от этого подхода, поскольку пользователь может оставить документ открытым и продолжить внесение в него дальнейших изменений. Наши веб-службы разблокируют документ, как только мы его выгружаем, поэтому мы не можем этого сделать, пока пользователь не завершит работу с ним.
Мы имеем дело только с документами, которые заблокированы их применением. Я ценю, что есть некоторые приложения, которые не блокируют их файлы, но мы не должны их рассматривать здесь.
методы Перспективы
Ниже 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;
}
Не можете ли вы отладить и пройти трассировку стека? Это кажется довольно странным, и я сомневаюсь, что трассировка стека будет повреждена (по крайней мере, с 'IOException') ... можете ли вы опубликовать код для' GetOutlookInternal'? – Jcl
Никогда не рекомендуется делиться файлами в окнах. Похоже, вы используете Outlook для доступа к файлам. Я думаю, что oledb используется для просмотра файлов. Oledb не предназначен для работы в многопользовательской среде и неправильно обрабатывает блокировку файлов. Даже если вы можете подумать, что вы блокируете файлы в C#, вы предотвращаете одновременный доступ двух файлов к файлам. это не означает, что другой процесс Windows не может получить доступ к файлу. Используете ли вы какие-либо процессы Async? – jdweng
@Jcl, к сожалению, проблема возникает только на некоторых клиентских ПК, и даже тогда это спорадически. Я не могу воспроизвести в отладчике. Я обновил, чтобы включить метод Outlook. –