Рекомендации по записи в файлы

Основные API

Разработчики иногда сталкиваются с рядом распространенных проблем при использовании методов Write классов FileIO и PathIO для выполнения операций ввода-вывода файловой системы. Например, распространенные проблемы включают:

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

Методы записи классов FileIO и PathIO включают следующие:

  • WriteBufferAsync
  • WriteBytesAsync
  • WriteLinesAsync
  • WriteTextAsync

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

Замечание

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

Удобство и управление

Объект StorageFile не является дескриптором файла, как родная модель программирования Win32. Вместо этого файл StorageFile представляет собой представление файла с методами для управления его содержимым.

Понимание этой концепции полезно при выполнении операций ввода-вывода с помощью StorageFile. Например, раздел "Запись в файл" представляет три способа записи в файл:

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

Модель транзакций

Методы записи классов FileIO и PathIO упаковывают шаги в третью модель записи, описанную выше, с добавленным слоем. Этот слой заключён в транзакцию хранения.

Чтобы защитить целостность исходного файла в случае ошибки при записи данных, методы записи используют транзакционные модели, открыв файл с помощью OpenTransactedWriteAsync. Этот процесс создает объект StorageStreamTransaction . После создания этого объекта транзакции API записывают данные, описанные в примере доступа к файлам , или в примере кода в статье StorageStreamTransaction .

На следующей схеме показаны базовые задачи, выполняемые методом WriteTextAsync в успешной операции записи. На этом рисунке представлено упрощенное представление операции. Например, он пропускает такие шаги, как кодировка текста и асинхронное завершение в разных потоках.

Схема последовательности вызовов API WinRT для записи в файл

Преимущества использования методов записи классов FileIO и PathIO вместо более сложной четырехэтапной модели с помощью потока:

  • Один вызов API для обработки всех промежуточных шагов, включая ошибки.
  • Исходный файл хранится, если что-то пошло не так.
  • Система будет поддерживаться в максимально чистом состоянии.

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

Распространенные коды ошибок для методов записи классов FileIO и PathIO

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

Имя ошибки (значение) Шаги Причины Решения
ERROR_ACCESS_DENIED (0X80070005) 5 Исходный файл может быть помечен для удаления, возможно, из предыдущей операции. Повторите операцию.
Убедитесь, что доступ к файлу синхронизирован.
Ошибка_Нарушения_Общего_Доступа (0x80070020) 5 Исходный файл открывается другой эксклюзивной записью. Повторите операцию.
Убедитесь, что доступ к файлу синхронизирован.
НЕВОЗМОЖНО УДАЛИТЬ ЗАМЕНЁННОЕ (0x80070497) 19-20 Исходный файл (file.txt) не удалось заменить, так как он используется. Еще один процесс или операция получили доступ к файлу до его замены. Повторите операцию.
Убедитесь, что доступ к файлу синхронизирован.
ERROR_DISK_FULL (0x80070070) 7, 14, 16, 20 Транзакция модели создает дополнительный файл, и это потребляет дополнительное хранилище.
Ошибка: недостаточно памяти (ERROR_OUTOFMEMORY, 0x8007000E) 14, 16 Это может произойти из-за нескольких невыполненных операций ввода-вывода или больших размеров файлов. Более детализированный подход, связанный с управлением потоком, может устранить ошибку.
E_FAIL (0x80004005) Любое Прочее Повторите операцию. Если это по-прежнему не удается, это ошибка платформы, и приложение должно завершить работу, так как оно находится в несогласованном состоянии.

Другие рекомендации по состоянию файла, которые могут привести к ошибкам

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

Данные записываются в файл только в том случае, если операция завершена

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

Читатели

Если файл, в который ведется запись, также используется вежливым читателем (то есть открыт с помощью FileAccessMode.Read), последующие операции чтения приведут к ошибке ERROR_OPLOCK_HANDLE_CLOSED (0x80070323). Иногда приложения повторно открывают файл для чтения, пока операция записи продолжается. Это может привести к состоянию гонки, в котором запись в конечном счете завершается ошибкой при попытке перезаписать исходный файл, так как его невозможно заменить.

Файлы из KnownFolders

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

Конфликт ввода-вывода

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

~TMP-файлы

Иногда, если операция принудительно отменена (например, если приложение было приостановлено или завершено ОС), транзакция не фиксируется или закрывается соответствующим образом. Это может оставить файлы с расширением (~TMP). При обработке активации приложения рекомендуется удалить эти временные файлы (если они существуют в локальных данных приложения).

Рекомендации, основанные на типах файлов

Некоторые ошибки могут стать более распространенными в зависимости от типа файлов, частоты доступа к ним и их размера. Как правило, есть три категории файлов, к которых может получить доступ ваше приложение:

  • Файлы, созданные и редактируемые пользователем в локальной папке данных приложения. Они создаются и редактируются только при использовании приложения, и они существуют только в приложении.
  • Метаданные приложения. Приложение использует эти файлы для отслеживания собственного состояния.
  • Другие файлы в местах файловой системы, где приложение объявило права доступа. Чаще всего они находятся в одном из известных папок.

Приложение имеет полный контроль над первыми двумя категориями файлов, так как они являются частью файлов пакета приложения и получают доступ исключительно к приложению. Для файлов в последней категории приложение должно учитывать, что другие приложения и службы ОС могут одновременно получать доступ к файлам.

В зависимости от приложения доступ к файлам может отличаться от частоты:

  • Очень низкий. Обычно это файлы, которые открываются один раз при запуске приложения и сохраняются при приостановке приложения.
  • Низко. Это файлы, с которыми пользователь специально выполняет действие (например, сохранение или загрузка).
  • Средний или высокий. Это файлы, в которых приложение должно постоянно обновлять данные (например, функции автосохранений или отслеживание метаданных констант).

Для размера файла рассмотрим данные о производительности в следующей диаграмме для метода WriteBytesAsync. На этой диаграмме сравнивается время завершения операции в зависимости от размера файла, по средней производительности 10000 операций для каждого размера файла в контролируемой среде.

Производительность WriteBytesAsync

Значения времени на оси y опущены намеренно из этой диаграммы, так как разные аппаратные и конфигурации будут выдавать разные абсолютные значения времени. Однако мы последовательно наблюдали эти тенденции в наших тестах:

  • Для очень небольших файлов (<= 1 МБ): операции выполняются consistently быстро.
  • Для больших файлов (> 1 МБ): время завершения операций начинает увеличиваться экспоненциально.

Ввод-вывод во время приостановки приложения

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

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

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

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

Пример см. в примере BasicSuspension .

Другие примеры и ресурсы

Ниже приведены несколько примеров и других ресурсов для конкретных сценариев.

Пример кода для повторных попыток ввода-вывода файла

Ниже приведен пример псевдокода о том, как повторить запись (C#), предполагая, что запись выполняется после того, как пользователь выбирает файл для сохранения:

Windows.Storage.Pickers.FileSavePicker savePicker = new Windows.Storage.Pickers.FileSavePicker();
savePicker.FileTypeChoices.Add("Plain Text", new List<string>() { ".txt" });
Windows.Storage.StorageFile file = await savePicker.PickSaveFileAsync();

Int32 retryAttempts = 5;

const Int32 ERROR_ACCESS_DENIED = unchecked((Int32)0x80070005);
const Int32 ERROR_SHARING_VIOLATION = unchecked((Int32)0x80070020);

if (file != null)
{
    // Application now has read/write access to the picked file.
    while (retryAttempts > 0)
    {
        try
        {
            retryAttempts--;
            await Windows.Storage.FileIO.WriteTextAsync(file, "Text to write to file");
            break;
        }
        catch (Exception ex) when ((ex.HResult == ERROR_ACCESS_DENIED) ||
                                   (ex.HResult == ERROR_SHARING_VIOLATION))
        {
            // This might be recovered by retrying, otherwise let the exception be raised.
            // The app can decide to wait before retrying.
        }
    }
}
else
{
    // The operation was cancelled in the picker dialog.
}

Синхронизация доступа к файлу

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

См. также