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


Хранение и получение параметров и других данных приложения

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

Важно отметить данные приложения: время существования данных приложения привязано к времени существования приложения. Если приложение удалено, все данные приложения будут потеряны в результате. Не используйте данные приложения для хранения пользовательских данных или каких-либо данных, которые пользователи могут воспринимать как ценные и незаменимые. Мы рекомендуем использовать библиотеки пользователя и Microsoft OneDrive для хранения таких сведений. Данные приложения идеально подходят для хранения пользовательских настроек, параметров и избранного для конкретного приложения.

Типы данных приложения

Существует два типа данных приложения: параметры и файлы.

Настройки

Используйте параметры для хранения параметров пользователя и сведений о состоянии приложения. API данных приложения позволяет легко создавать и извлекать параметры (мы рассмотрим некоторые примеры далее в этой статье).

Ниже приведены типы данных, которые можно использовать для параметров приложения:

  • UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double
  • Boolean
  • Char16, String
  • DateTime, TimeSpan
  • GUID, точка, размер, прямоугольник
  • ApplicationDataCompositeValue: набор связанных параметров приложения, которые должны сериализоваться и десериализировать атомарно. Используйте составные параметры, чтобы легко обрабатывать атомарные обновления параметров взаимозависимости. Система обеспечивает целостность составных параметров во время параллельного доступа и перемещения. Составные параметры оптимизированы для небольших объемов данных, и производительность может быть плохой, если они используются для больших наборов данных.

Файлы

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

Хранение данных приложения в хранилищах данных приложения

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

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

Данные локального приложения

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

Получение локального хранилища данных приложения

Прежде чем считывать или записывать данные локального приложения, необходимо получить локальное хранилище данных приложения. Чтобы получить локальное хранилище данных приложения, используйте свойство ApplicationData.LocalSettings , чтобы получить локальные параметры приложения в качестве объекта ApplicationDataContainer . Используйте свойство ApplicationData.LocalFolder, чтобы получить файлы в объекте StorageFolder. Используйте свойство ApplicationData.LocalCacheFolder, чтобы получить папку в локальном хранилище данных приложения, где можно сохранить файлы, которые не включены в резервное копирование и восстановление.

Windows.Storage.ApplicationDataContainer localSettings = 
    Windows.Storage.ApplicationData.Current.LocalSettings;
Windows.Storage.StorageFolder localFolder = 
    Windows.Storage.ApplicationData.Current.LocalFolder;

Создание и извлечение простого локального параметра

Чтобы создать или записать параметр, используйте свойство ApplicationDataContainer.Values для доступа к параметрам в контейнере localSettings , который мы получили на предыдущем шаге. В этом примере создается параметр с именем exampleSetting.

// Simple setting

localSettings.Values["exampleSetting"] = "Hello Windows";

Чтобы получить этот параметр, используйте то же свойство ApplicationDataContainer.Values , которое использовалось для создания параметра. В этом примере показано, как получить только что созданный параметр.

// Simple setting
Object value = localSettings.Values["exampleSetting"];

Создание и получение локального составного значения

Чтобы создать или записать составное значение, создайте объект ApplicationDataCompositeValue . В этом примере создается составной параметр с именем exampleCompositeSetting и добавляется в localSettings контейнер.

// Composite setting

Windows.Storage.ApplicationDataCompositeValue composite = 
    new Windows.Storage.ApplicationDataCompositeValue();
composite["intVal"] = 1;
composite["strVal"] = "string";

localSettings.Values["exampleCompositeSetting"] = composite;

В этом примере показано, как получить только что созданное составное значение.

// Composite setting

Windows.Storage.ApplicationDataCompositeValue composite = 
   (Windows.Storage.ApplicationDataCompositeValue)localSettings.Values["exampleCompositeSetting"];

if (composite == null)
{
   // No data
}
else
{
   // Access data in composite["intVal"] and composite["strVal"]
}

Создание и чтение локального файла

Чтобы создать и обновить файл в локальном хранилище данных приложения, используйте API-интерфейсы файлов, такие как Windows.StorageFolder.CreateFileAsync и Windows.Storage.FileIO.WriteTextAsync. В этом примере создается файл с именем dataFile.txt в контейнере localFolder и записывается текущая дата и время в файл. Значение ReplaceExisting из перечисления CreationCollisionOption указывает, чтобы заменить файл, если он уже существует.

async void WriteTimestamp()
{
   Windows.Globalization.DateTimeFormatting.DateTimeFormatter formatter = 
       new Windows.Globalization.DateTimeFormatting.DateTimeFormatter("longtime");

   StorageFile sampleFile = await localFolder.CreateFileAsync("dataFile.txt", 
       CreationCollisionOption.ReplaceExisting);
   await FileIO.WriteTextAsync(sampleFile, formatter.Format(DateTimeOffset.Now));
}

Чтобы открыть и прочитать файл в локальном хранилище данных приложения, используйте API-интерфейсы файлов, такие как Windows.StorageFolder.GetFileAsync, Windows.StorageFile.GetFileFromApplicationUriAsync и Windows.Storage.FileIO.ReadTextAsync. В этом примере файл, dataFile.txt созданный на предыдущем шаге, считывает дату из файла. Дополнительные сведения о загрузке файлового ресурса из различных расположений см. в разделе "Загрузка файлового ресурса".

async void ReadTimestamp()
{
   try
   {
      StorageFile sampleFile = await localFolder.GetFileAsync("dataFile.txt");
      String timestamp = await FileIO.ReadTextAsync(sampleFile);
      // Data is contained in timestamp
   }
   catch (Exception)
   {
      // Timestamp not found
   }
}

Перемещаемые данные

Предупреждение

Перемещаемые данные и параметры больше не поддерживаются в Windows 11. Рекомендуемая замена — Служба приложений Azure. Служба приложений Azure широко поддерживается и хорошо описана. Это надежное решение, которое поддерживает сценарии с использованием разных платформ и экосистем, включая iOS, Android и интернет-решения.

Следующая документация относится к Windows 10 версии 1909 и более низкой.

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

ОС ограничивает размер данных приложения, которые может перемещать каждое приложение. См. раздел ApplicationData.RoamingStorageQuota. Если приложение достигнет этого ограничения, ни один из данных приложения не будет реплицироваться в облако, пока общий объем перемещаемых данных приложения не будет меньше предела. По этой причине рекомендуется использовать перемещаемые данные только для пользовательских предпочтений, ссылок и небольших файлов данных.

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

Перемещаемые данные выполняются и не выполняются

См. важное примечание о перемещении данных.

  • Используйте роуминг для пользовательских настроек и настроек, ссылок и небольших файлов данных. Например, используйте роуминг для сохранения предпочтения цвета фона пользователя на всех устройствах.
  • Используйте перемещение, чтобы пользователи продолжали задачу на разных устройствах. Например, перемещайте данные приложения, такие как содержимое черновика электронной почты или недавно просматриваемую страницу в приложении чтения.
  • Обработайте событие DataChanged , обновив данные приложения. Это событие возникает, когда данные приложения только что завершили синхронизацию из облака.
  • Перемещайте ссылки на содержимое, а не необработанные данные. Например, перемещайте URL-адрес, а не содержимое веб-статьи.
  • Для важных параметров времени используйте параметр HighPriority, связанный с RoamingSettings.
  • Не перемещайте данные приложения, относящиеся к устройству. Некоторые сведения имеют отношение только локально, например имя пути к локальному ресурсу файла. Если вы решите перемещать локальную информацию, убедитесь, что приложение может восстановиться, если сведения не действительны на дополнительном устройстве.
  • Не перемещайте большие наборы данных приложения. Существует ограничение на объем данных приложения, которые может перемещать приложение; используйте свойство RoamingStorageQuota , чтобы получить это максимальное значение. Если приложение достигает этого ограничения, данные не могут перемещаться до тех пор, пока размер хранилища данных приложения больше не превышает предел. При разработке приложения рассмотрите, как поместить привязку к большим данным, чтобы не превышать предел. Например, если для сохранения игрового состояния требуется 10 КБ, приложение может разрешить только 10 игр.
  • Не используйте роуминг для данных, основанных на мгновенной синхронизации. Windows не гарантирует мгновенной синхронизации; Перемещение может быть значительно отложено, если пользователь находится в автономном режиме или в сети с высокой задержкой. Убедитесь, что пользовательский интерфейс не зависит от мгновенной синхронизации.
  • Не используйте перемещение для часто изменяющихся данных. Например, если ваше приложение отслеживает часто изменяющиеся сведения, например положение в песне на секунду, не сохраняйте это как перемещаемые данные приложения. Вместо этого выберите менее частое представление, которое по-прежнему обеспечивает хороший пользовательский интерфейс, как и в настоящее время играющая песня.

Предварительные требования для роуминга

См. важное примечание о перемещении данных.

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

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

Разрешение конфликтов

См. важное примечание о перемещении данных.

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

Когда записывать данные

См. важное примечание о перемещении данных.

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

Чрезмерная защита использования

См. важное примечание о перемещении данных.

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

Управление версиями

См. важное примечание о перемещении данных.

Данные приложения могут использовать управление версиями для обновления одной структуры данных до другой. Номер версии отличается от версии приложения и может быть задан. Хотя и не применяется, настоятельно рекомендуется использовать дополнительные номера версий, так как нежелательные осложнения (включая потерю данных)могут возникнуть при попытке перейти на более низкий номер версии данных, представляющий новые данные.

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

Тестирование и средства

См. важное примечание о перемещении данных.

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

  • Перемещаемые данные не превышают максимальный размер (дополнительные сведения см. в разделе RoamingStorageQuota).
  • Файлы закрыты и освобождены должным образом.
  • Существует по крайней мере два устройства с одной и той же версией приложения.

Регистрация для получения уведомления при изменении перемещаемых данных

См. важное примечание о перемещении данных.

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

  1. Зарегистрируйтесь для получения уведомления при изменении перемещаемых данных.

    Событие DataChanged уведомляет вас об изменении перемещаемых данных. Этот пример задает DataChangeHandler обработчик для изменения перемещаемых данных.

void InitHandlers()
    {
       Windows.Storage.ApplicationData.Current.DataChanged += 
          new TypedEventHandler<ApplicationData, object>(DataChangeHandler);
    }

    void DataChangeHandler(Windows.Storage.ApplicationData appData, object o)
    {
       // TODO: Refresh your data
    }
  1. Получите контейнеры для параметров и файлов приложения.

    Используйте свойство ApplicationData.RoamingSettings, чтобы получить параметры и свойство ApplicationData.RoamingFolder, чтобы получить файлы.

Windows.Storage.ApplicationDataContainer roamingSettings = 
        Windows.Storage.ApplicationData.Current.RoamingSettings;
    Windows.Storage.StorageFolder roamingFolder = 
        Windows.Storage.ApplicationData.Current.RoamingFolder;

Создание и получение параметров роуминга

См. важное примечание о перемещении данных.

Используйте свойство ApplicationDataContainer.Values для доступа к параметрам в контейнереroamingSettings, который мы получили в предыдущем разделе. В этом примере создается простой параметр с именем exampleSetting compositeи составным значением.

// Simple setting

roamingSettings.Values["exampleSetting"] = "Hello World";
// High Priority setting, for example, last page position in book reader app
roamingSettings.values["HighPriority"] = "65";

// Composite setting

Windows.Storage.ApplicationDataCompositeValue composite = 
    new Windows.Storage.ApplicationDataCompositeValue();
composite["intVal"] = 1;
composite["strVal"] = "string";

roamingSettings.Values["exampleCompositeSetting"] = composite;

В этом примере извлекаются только что созданные параметры.

// Simple setting

Object value = roamingSettings.Values["exampleSetting"];

// Composite setting

Windows.Storage.ApplicationDataCompositeValue composite = 
   (Windows.Storage.ApplicationDataCompositeValue)roamingSettings.Values["exampleCompositeSetting"];

if (composite == null)
{
   // No data
}
else
{
   // Access data in composite["intVal"] and composite["strVal"]
}

Создание и извлечение перемещаемых файлов

См. важное примечание о перемещении данных.

Чтобы создать и обновить файл в хранилище данных перемещаемого приложения, используйте API-интерфейсы файлов, такие как Windows.Storage.StorageFolder.CreateFileAsync и Windows.Storage.FileIO.WriteTextAsync. В этом примере создается файл с именем dataFile.txt в контейнере roamingFolder и записывается текущая дата и время в файл. Значение ReplaceExisting из перечисления CreationCollisionOption указывает, чтобы заменить файл, если он уже существует.

async void WriteTimestamp()
{
   Windows.Globalization.DateTimeFormatting.DateTimeFormatter formatter = 
       new Windows.Globalization.DateTimeFormatting.DateTimeFormatter("longtime");

   StorageFile sampleFile = await roamingFolder.CreateFileAsync("dataFile.txt", 
       CreationCollisionOption.ReplaceExisting);
   await FileIO.WriteTextAsync(sampleFile, formatter.Format(DateTimeOffset.Now));
}

Чтобы открыть и прочитать файл в хранилище данных перемещаемого приложения, используйте API-интерфейсы файлов, такие как Windows.Storage.StorageFolder.GetFileAsync, Windows.StorageFile.GetFileFromApplicationUriAsync и Windows.Storage.FileIO.ReadTextAsync. В этом примере открывается dataFile.txt файл, созданный в предыдущем разделе, и считывается дата из файла. Дополнительные сведения о загрузке файлового ресурса из различных расположений см. в разделе "Загрузка файлового ресурса".

async void ReadTimestamp()
{
   try
   {
      StorageFile sampleFile = await roamingFolder.GetFileAsync("dataFile.txt");
      String timestamp = await FileIO.ReadTextAsync(sampleFile);
      // Data is contained in timestamp
   }
   catch (Exception)
   {
      // Timestamp not found
   }
}

Временные данные приложения

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

Получение временного контейнера данных

Используйте свойство ApplicationData.TemporaryFolder для получения файлов. Следующие шаги используют temporaryFolder переменную из этого шага.

Windows.Storage.StorageFolder temporaryFolder = ApplicationData.Current.TemporaryFolder;

Создание и чтение временных файлов

Чтобы создать и обновить файл во временном хранилище данных приложения, используйте API-интерфейсы файлов, такие как Windows.Storage.StorageFolder.CreateFileAsync и Windows.Storage.FileIO.WriteTextAsync. В этом примере создается файл с именем dataFile.txt в контейнере temporaryFolder и записывается текущая дата и время в файл. Значение ReplaceExisting из перечисления CreationCollisionOption указывает, чтобы заменить файл, если он уже существует.

async void WriteTimestamp()
{
   Windows.Globalization.DateTimeFormatting.DateTimeFormatter formatter = 
       new Windows.Globalization.DateTimeFormatting.DateTimeFormatter("longtime");

   StorageFile sampleFile = await temporaryFolder.CreateFileAsync("dataFile.txt", 
       CreateCollisionOption.ReplaceExisting);
   await FileIO.WriteTextAsync(sampleFile, formatter.Format(DateTimeOffset.Now));
}

Чтобы открыть и прочитать файл во временном хранилище данных приложения, используйте API-интерфейсы файлов, такие как Windows.Storage.StorageFolder.GetFileAsync, Windows.StorageFile.GetFileFromApplicationUriAsync и Windows.Storage.FileIO.ReadTextAsync. В этом примере файл, dataFile.txt созданный на предыдущем шаге, считывает дату из файла. Дополнительные сведения о загрузке файлового ресурса из различных расположений см. в разделе "Загрузка файлового ресурса".

async void ReadTimestamp()
{
   try
   {
      StorageFile sampleFile = await temporaryFolder.GetFileAsync("dataFile.txt");
      String timestamp = await FileIO.ReadTextAsync(sampleFile);
      // Data is contained in timestamp
   }
   catch (Exception)
   {
      // Timestamp not found
   }
}

Упорядочение данных приложения с помощью контейнеров

Для организации параметров и файлов данных приложения можно создавать контейнеры (представленные объектами ApplicationDataContainer ), а не работать непосредственно с каталогами. Контейнеры можно добавлять в локальные, перемещаемые и временные хранилища данных приложения. Контейнеры можно вложить до 32 уровней глубоко.

Чтобы создать контейнер параметров, вызовите метод ApplicationDataContainer.CreateContainer . В этом примере создается локальный контейнер параметров с именем exampleContainer и добавляется параметр с именем exampleSetting. Значение Always из перечисления ApplicationDataCreateDisposition указывает, что контейнер создается, если он еще не существует.

Windows.Storage.ApplicationDataContainer localSettings = 
    Windows.Storage.ApplicationData.Current.LocalSettings;
Windows.Storage.StorageFolder localFolder = 
    Windows.Storage.ApplicationData.Current.LocalFolder;

// Setting in a container
Windows.Storage.ApplicationDataContainer container = 
   localSettings.CreateContainer("exampleContainer", Windows.Storage.ApplicationDataCreateDisposition.Always);

if (localSettings.Containers.ContainsKey("exampleContainer"))
{
   localSettings.Containers["exampleContainer"].Values["exampleSetting"] = "Hello Windows";
}

Удаление параметров и контейнеров приложений

Чтобы удалить простой параметр, который приложение больше не требуется, используйте метод ApplicationDataContainerSettings.Remove . В этом примере удаляется локальный exampleSetting параметр, созданный ранее.

Windows.Storage.ApplicationDataContainer localSettings = 
    Windows.Storage.ApplicationData.Current.LocalSettings;
Windows.Storage.StorageFolder localFolder = 
    Windows.Storage.ApplicationData.Current.LocalFolder;

// Delete simple setting

localSettings.Values.Remove("exampleSetting");

Чтобы удалить составной параметр, используйте метод ApplicationDataCompositeValue.Remove . В этом примере удаляется локальный exampleCompositeSetting составной параметр, созданный в предыдущем примере.

Windows.Storage.ApplicationDataContainer localSettings = 
    Windows.Storage.ApplicationData.Current.LocalSettings;
Windows.Storage.StorageFolder localFolder = 
    Windows.Storage.ApplicationData.Current.LocalFolder;

// Delete composite setting

localSettings.Values.Remove("exampleCompositeSetting");

Чтобы удалить контейнер, вызовите метод ApplicationDataContainer.DeleteContainer . В этом примере удаляется созданный ранее контейнер локальных exampleContainer параметров.

Windows.Storage.ApplicationDataContainer localSettings = 
    Windows.Storage.ApplicationData.Current.LocalSettings;
Windows.Storage.StorageFolder localFolder = 
    Windows.Storage.ApplicationData.Current.LocalFolder;

// Delete container

localSettings.DeleteContainer("exampleContainer");

Управление версиями данных приложения

При необходимости можно использовать версию данных приложения для приложения. Это позволит создать будущую версию приложения, которая изменяет формат данных приложения без возникновения проблем совместимости с предыдущей версией приложения. Приложение проверяет версию данных приложения в хранилище данных, а если версия меньше версии, ожидаемой приложением, приложение должно обновить данные приложения до нового формата и обновить версию. Дополнительные сведения см. в свойстве Application.Version и методе ApplicationData.SetVersionAsync.