System.IO.File.Delete () / System.IO.File.Move () иногда не работает - PullRequest
5 голосов
/ 24 июня 2011

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

Таким образом, мы изменили алгоритм для сохранения во временном файле, а затем для удаления реального файла и выполнения перемещения.

Наш код в настоящее время выглядит следующим образом ..

private void Save()
{
    XmlTextWriter streamWriter = null;
    try
    {
        streamWriter = new XmlTextWriter(xmlTempFilePath, System.Text.Encoding.UTF8);

        XmlSerializer xmlSerializer = new XmlSerializer(typeof(MyCollection));

        xmlSerializer.Serialize(streamWriter, myCollection);

        if (streamWriter != null)
            streamWriter.Close();

        // Delete the original file
        System.IO.File.Delete(xmlFilePath);

        // Do a move over the top of the original file 
        System.IO.File.Move(xmlTempFilePath, xmlFilePath);
    }
    catch (System.Exception ex)
    {
        throw new InvalidOperationException("Could not save the xml file.", ex);
    }
    finally
    {
        if (streamWriter != null)
            streamWriter.Close();
    }
}

Это работает в лаборатории и на производстве почти все время. Программа работает на 12 компьютерах, и этот код вызывается в среднем один раз каждые 5 минут. Примерно один или два раза в день мы получаем это исключение:

System.InvalidOperationException: 
Could not save the xml file. 
---> System.IO.IOException: Cannot create a file when that file already exists.
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.__Error.WinIOError()
at System.IO.File.Move(String sourceFileName, String destFileName)
at MyApp.MyNamespace.InternalSave()

Это похоже на то, как если бы удаление фактически не выдавалось на жесткий диск перед выполнением перемещения.

Это происходит на машинах Win7.

Пара вопросов: существует ли какая-то концепция Flush(), которую можно использовать для всей дисковой операционной системы? Это ошибка с моим кодом, .net, ОС или чем-то еще? Должен ли я положить в Thread.Sleep(x)? Может мне стоит сделать File.Copy(src, dest, true)? Должен ли я написать следующий код? (Но это выглядит довольно глупо.)

while (System.IO.File.Exists(xmlFilePath))
{
    System.IO.File.Delete(xmlFilePath);
}

// Do a move over the top of the main file 
bool done = false;
while (!done)
{
    try
    {
        System.IO.File.Move(xmlTempFilePath, xmlFilePath);
        done = true;
    }
    catch (System.IO.IOException)
    {
        // let it loop
    }
}

Кто-нибудь видел это раньше?

Ответы [ 4 ]

13 голосов
/ 24 июня 2011

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

Такие программы открывают файл и пытаются свести к минимуму влияние, указав доступ для удаления общего ресурса. Это доступно и в .NET, это опция FileShare.Delete. С этой опцией Windows позволяет процессу удалить файл, даже если он открыт. Он внутренне помечается как «ожидающий удаления». Файл на самом деле не удаляется из файловой системы, он все еще остается после вызова File.Delete. Любой, кто пытается открыть файл после этого, получает ошибку «Отказано в доступе». Файл фактически не удаляется до тех пор, пока последний дескриптор объекта файла не будет закрыт.

Вы, вероятно, можете видеть, куда это движется, это объясняет, почему File.Delete преуспел, но File.Move не удалось. Что вам нужно сделать, это File.Move файла в первую очередь, чтобы он имел другое имя. Затем переименуйте новый файл, затем удалите оригинал. Самое первое, что вы делаете, это удаляете возможную случайную копию с переименованным именем, она могла остаться после сбоя питания.

Подведение итогов:

  1. Создать файл.new
  2. Удалить file.tmp
  3. Переименование file.xml в file.tmp
  4. Переименовать файл.new в файл file.xml
  5. Удалить file.tmp

Сбой шага 5 не является критическим.

3 голосов
/ 24 июня 2011

как насчет использования Move.Copy с установленным значением true для перезаписи, чтобы оно перезаписывало состояние вашего приложения, а затем вы можете удалить свое временное состояние?

Вы также можете присоединиться к событию App_Exit ипопытаться выполнить чистое отключение?

1 голос
/ 24 июня 2011

Если несколько потоков в этом приложении могут вызывать Save, или если несколько экземпляров программы пытаются обновить один и тот же файл (например, на сетевом ресурсе), вы можете получить условие состязания, чтобы файл не существуют, когда оба потока / процесса пытаются удалить, тогда один из них успешно выполняет Move (или Move в процессе), когда вторая попытка использовать то же имя файла и эта вторая Move завершится неудачей.

Как говорит Анварбек Раупов, вы можете использовать File.Copy(String, String, Boolean), чтобы разрешить перезапись (так что больше не нужно удалять), но это означает, что победит последний обновитель - вам нужно учитывать, если это что вы хотите (особенно в многопоточных сценариях, где последний обновитель, если вам не повезло, работал со старым состоянием).

Безопаснее было бы заставить каждый поток / процесс использовать отдельные файлы или реализовать некоторую форму блокировки на основе файловой системы (например, создать другой файл, чтобы объявить «Я работаю над этим файлом», обновить основной файл, затем удалить этот файл блокировки).

0 голосов
/ 24 июня 2011

Как сказал Ганс, обходной путь - сначала переместить файл, а затем удалить его.

Тем не менее, при удалении Transactional NTFS я не смог воспроизвести описанную ошибку.Проверьте github.com/haf/Castle.Transactions и соответствующие пакеты nuget ... 2.5 хорошо протестирован и задокументирован для файловых транзакций.

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

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

...