Как узнать, связано ли обнаруженное IOException с файлом, используемым другим процессом, не прибегая к анализу свойства Message

Когда я открываю файл, я хочу знать, используется ли он другим процессом, поэтому я могу выполнять специальную обработку; любое другое исключение IO. Свойство Message IOException содержит "Процесс не может получить доступ к файлу" foo ", потому что он используется другим процессом.", Но это не подходит для программного обнаружения. Что является самым безопасным, наиболее надежным способом обнаружения файла, используемого другим процессом?

Ответ 1

Эта конкретная версия IOException вызывается, когда код ошибки, возвращаемый из встроенной функции Win32, ERROR_SHARING_VIOLATION (Документация). Он имеет числовое значение 0x20, но фактически хранится как 0x80070020 в свойстве HRESULT исключения (это результат вызова MakeHRFromErrorCode).

Таким образом, программный способ проверки нарушения совместного доступа проверяет свойство HRESULT на IOException для значения 0x80070020.

public static bool IsSharingViolation(this IOException ex) {
  return 0x80070020 == Marshal.GetHRForException(ex);
}

Однако я задаюсь вопросом, что именно вы хотите сделать в сценарии, который он выбрал в результате нарушения доступа. В тот момент, когда исключение выбрасывается, другой процесс может выйти и, следовательно, удалить нарушение.

Ответ 2

Мне не хватает "репутации" для комментариев, так что, надеюсь, этот "ответ" в порядке...

Принятый ответ - именно то, что я искал и работает отлично, но люди здесь и на подобных вопросах задали вопрос об утилите проверки того, заблокирован ли файл. Правильно, что функция утилиты для проверки, если файл заблокирован, не очень удобен, потому что в самом следующем утверждении состояние могло измениться.

Но шаблон попытки операции блокировки, а затем по-разному реагирует на ошибки блокировки и общие ошибки, является допустимым и полезным. Самое очевидное, что нужно подождать немного и повторить операцию. Это можно обобщить на вспомогательную функцию, подобную этой:

protected static void RetryLock(Action work) {
    // Retry LOCK_MAX_RETRIES times if file is locked by another process
    for (int i = 1; i <= LOCK_MAX_RETRIES; i++) {
        try {
            work();
            return;
        } catch (IOException ex) {
            if (i == LOCK_MAX_RETRIES || (uint) ex.HResult != 0x80070020) {
                throw;
            } else {
                // Min should be long enough to generally allow other process to finish
                // while max should be short enough such that RETRIES * MAX isn't intolerable
                Misc.SleepRandom(LOCK_MIN_SLEEP_MS, LOCK_MAX_SLEEP_MS);
            }
        }
    }
} // RetryLock

..., который затем можно использовать следующим образом:

public string DoSomething() {
    string strReturn = null;
    string strPath = @"C:\Some\File\Path.txt";
    // Do some initial work...

    //----------------------------------------------------------------------------------------------------
    // NESTED FUNCTION to do main logic, RetryLock will retry up to N times on lock failures
    Action doWork = delegate {
        using (FileStream objFile = File.Open(strPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)) {
            // Does work here if lock succeeded, else File.Open will throw ex
            strReturn = new StreamReader(objFile).ReadLine();
        }
    }; // delegate doWork
    //----------------------------------------------------------------------------------------------------

    RetryLock(doWork); // Throws original ex if non-locking related or tried max times
    return strReturn;
}

... в любом случае, просто публиковать в случае, если кто-то с подобными потребностями находит шаблон полезным.