Поделиться через


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

Основные API

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

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

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

  • WriteBufferAsync
  • WriteBytesAsync
  • WriteLinesAsync
  • WriteTextAsync

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

Замечание

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

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

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

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

  • Использование метода FileIO.WriteTextAsync .
  • Создав буфер и вызвав метод FileIO.WriteBufferAsync .
  • Четырехэтапная модель с помощью потока:
    1. Открыть файл, чтобы получить поток.
    2. Получить выходной поток.
    3. Создайте объект DataWriter и вызовите соответствующий метод записи .
    4. Зафиксировать данные в устройстве записи данных и очистить выходной поток.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Читатели

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

Файлы из KnownFolders

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

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

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

~TMP-файлы

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

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

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

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

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

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

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

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

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

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

  • Для очень небольших файлов (<= 1 МБ): время выполнения операций постоянно быстрое.
  • Для больших файлов (> 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 описывается, как поддерживать исключительный доступ к файлу для записи, позволяя одновременное чтение. Помните, что сериализация операций ввода-вывода влияет на производительность.

См. также