Использование асинхронности в среде выполнения Windows для создания быстрых и гибких приложений

Блог для разработчиков приложений для Windows 8

Подробности создания Metro-приложений для Windows 8 от группы разработчиков Windows 8

Использование асинхронности в среде выполнения Windows для создания быстрых и гибких приложений

  • Comments 0

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

Быстрые и гибкие приложения должны быстро реагировать

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

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

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

Приложение не является быстрым и гибким, если оно перестает отвечать на ввод данных пользователем. Почему же приложения перестают отвечать? Одна из основных причин состоит в том, что приложение является синхронным, то есть оно ждет окончания какого-то другого процесса (например, получения данных из Интернета) и не может отреагировать на вводимые вами данные.

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

Учитывать характерное для современного мира постоянное подключение к сети — таким был основной принцип, когда мы начали разрабатывать API-интерфейсы для среды выполнения Windows (WinRT). Мы чувствовали, что важно предоставить мощную поверхность API, которая способствовала появлению по умолчанию быстрых и гибких приложений.

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

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

Чтобы лучше понять асинхронную природу WinRT, мы сначала обсудим, как вы можете использовать эти асинхронные API-интерфейсы простым способом. Затем мы рассмотрим внутренние механизмы и узнаем, какие асинхронные примитивы (на которых основаны новые языковые возможности) представлены в WinRT и как они работают.

Как это использовать: новые усовершенствованные средства разработки для реализации асинхронности

В прошлом написание асинхронного кода было сложным делом. Наши рабочие группы, создающие средства для приложений Windows 8 в стиле Metro, значительно продвинулись в решении этой задачи в недавно выпущенных версиях. В .NET Framework 4.0 мы представили Библиотеку параллельных задач. Затем ключевое слово await было представлено в C# и Visual Basic. C++ способствовал развитию таких технологий, как Библиотека параллельных шаблонов (PPL). А в JavaScript имеются такие замечательные средства, как Обещания.

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

В рассматриваемом случае с вашим любимым новостным приложением используется API-интерфейс Windows.Web.Syndication.SyndicationClient и достаточно простой код, продолжающий реагировать во время получения новостных статей. Вызов RetrieveFeedAsync возвращается немедленно. Это здорово, поскольку для получения результата из Интернета может потребоваться много времени. И в это время пользовательский интерфейс продолжает взаимодействовать с пользователем.

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

Код JavaScript, используемый для асинхронного получения RSS-канала:
var title;
var feedUri = new Windows.Foundation.Uri("http://www.devhawk.com/rss.xml");
var client = new Windows.Web.Syndication.SyndicationClient();

client.retrieveFeedAsync(feedUri).done(function(feed) {
title = feed.title.text;
});
 
Код C#, используемый для асинхронного получения RSS-канала:
var feedUri = new Uri("http://www.devhawk.com/rss.xml");
var client = new Windows.Web.Syndication.SyndicationClient();
var feed = await client.RetrieveFeedAsync(feedUri);
var title = feed.Title.Text;

Код VB, используемый для асинхронного получения RSS-канала:
Dim feedUri = New Uri("http://www.devhawk.com/rss.xml");
Dim client = New Windows.Web.Syndication.SyndicationClient();
Dim feed = Await client.RetrieveFeedAsync(feedUri);
Dim title = feed.Title.Text;
 

Код C++, используемый для асинхронного получения RSS-канала:
auto feedUri = ref new Windows::Foundation::Uri("http://www.devhawk.com/rss.xml");
auto client = ref new Windows::Web::Syndication::SyndicationClient();

task<SyndicationFeed^> retrieveTask(client->RetrieveFeedAsync(feedUri));
retrieveTask.then([this] (SyndicationFeed^ feed) {
this->title = feed->Title->Text;
});

 

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

Как это использовать: обработка ошибок

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

В JavaScript мы рекомендуем всегда ставить done в конце цепочки обещаний. Это приводит к тому, что исключения, захваченные в цепочке обещаний, становятся видимыми для разработчика (то есть о них сообщается в обработчике ошибок, или они выбрасываются). Done имеет такую же сигнатуру, как и then. Таким образом, для обработки ошибок вы просто передаете делегат ошибки в done():

 

var title;
var feedUri = new Windows.Foundation.Uri("http://www.devhawk.com/rss.xml");
var client = new Windows.Web.Syndication.SyndicationClient();

client.retrieveFeedAsync(feedUri).done(function(feed) {
title = feed.title.text;
}, function(error) {
console.log('an error occurred: ');
console.dir(error);
}
);

Для обработки исключения в C# или Visual Basic можно использовать блок try/catch таким же образом, как вы сегодня используете синхронный код:

 

var title;
var feedUri = new Uri("http://www.devhawk.com/rss.xml");
var client = new Windows.Web.Syndication.SyndicationClient();

try
{

var feed = await client.RetrieveFeedAsync(feedUri);
title = feed.Title.Text;
}
catch (Exception ex)
{
// An exception occurred from the async operation
}

Наиболее общий метод, применяемый в C++ для обработки асинхронных ошибок, состоит в использовании другого основанного на задачах продолжения, выдающего исключения (другие методы обработки ошибок в C++ можно найти в разделе, посвященном асинхронному программированию в C++, в центре разработчиков):

auto feedUri = ref new Windows::Foundation::Uri("http://www.devhawk.com/rss.xml");
auto client = ref new Windows::Web::Syndication::SyndicationClient();

task<SyndicationFeed^> retrieveTask(client->RetrieveFeedAsync(feedUri));
retrieveTask.then([this] (SyndicationFeed^ feed) {
this->title = feed->Title->Text;
}).then([] (task<void> t) {
try
{
t.get();
// .get() didn't throw, so we succeeded
}
catch (Exception^ ex)
{
// An error occurred
}
})
;

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

Как это работает: асинхронные примитивы в WinRT

Асинхронность — очень мощное средство, но может казаться, что для вас многое происходит словно по волшебству. Чтобы в том, что происходит, не осталось ничего таинственного, давайте рассмотрим более подробно, как работает асинхронность в WinRT. Нашим первым шагом будет исследование примитивов, на которых строится модель. Большинству из вас совсем не потребуется использовать эти примитивы (если найдется сценарий, в котором вам это нужно, мы будем рады получить ваши отзывы).

Давайте начнем с нашего кода C# и посмотрим на фактический тип возвращаемого значения метода RetrieveFeedAsync (показан здесь в C# Intellisense):

 Отображение Intellisense для метода RetrieveFeedAsync
 Рис 1. Отображение Intellisense для метода RetrieveFeedAsync

RetrieveFeedAsync возвращает интерфейс IAsyncOperationWithProgress. IAsyncOperationWithProgress — это один из пяти интерфейсов, определяющих основную поверхность асинхронного программирования для WinRT: IAsyncInfo, IAsyncAction, IAsyncActionWithProgress, IAsyncOperation и IAsyncOperationWithProgress.

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

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

 

Таблица, в которой представлены различные сочетания результатов и хода выполнения   
Рис. 2. Различные сочетания результатов и хода выполнения

Предоставление основных асинхронных примитивов в среде выполнения Windows позволяет C#, Visual Basic, C++ и JavaScript проецировать асинхронные операции WinRT привычным вам способом.

Примечание. Переход от холодного запуска к горячему

В версии Windows 8 Developer Preview, выпущенной на конференции //build, все асинхронные операции в WinRT запускались с помощью холодного запуска. Они не начинали выполняться немедленно. Для них требовался явный запуск разработчиком или неявный запуск с использованием асинхронных функций в C#, Visual Basic, JavaScript или C++. Мы получили из нескольких источников отзывы по этой проблеме, в которых отмечались сложность, запутанность и недостаточная интуитивность данного подхода.

Вы заметите, что в версии Windows 8 Consumer Preview мы удалили метод Start() из асинхронных примитивов WinRT. Теперь все наши асинхронные операции запускаются посредством горячего запуска. Таким образом, асинхронный метод запустил операцию, прежде чем возвращать ее вызывающей стороне.

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

Как это работает: результаты, отмена и ошибки с асинхронными примитивами

Асинхронная операция начинается в состоянии Started (Запущена) и может перейти в одно из трех следующих состояний: Canceled (Отменена), Completed (Завершена) и Error (Ошибка). Текущее состояние асинхронной операции отражается в свойстве Status (Состояние) этой асинхронной операции (представлено объектом AsyncStatus).

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

IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress> op;
op = client.RetrieveFeedAsync(feedUri);

op.Completed = (info, status) =>
{
SyndicationFeed feed = info.GetResults();
UpdateAppWithFeed(feed);
};

Иногда может потребоваться отменить операцию. Это можно сделать, вызвав метод Cancel для асинхронной операции.

IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress> op;
op = client.RetrieveFeedAsync(feedUri);
op.Cancel();

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

IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress> op;
op = client.RetrieveFeedAsync(feedUri);
op.Cancel();

op.Completed = (info, status) =>
{
if (status == AsyncStatus.Completed)
{
SyndicationFeed feed = info.GetResults();
UpdateAppWithFeed(feed);
}
else if (status == AsyncStatus.Canceled)
{
// Operation canceled
}
};

Этот код по-прежнему ненадежный, если происходит сбой операции. Подобно отмене, сообщение об ошибке поддерживается через ту же асинхронную операцию. Помимо использования объекта AsyncStatus для различения состояний Completed (Завершена) и Canceled (Отменена), мы также используем его для определения сбоя асинхронной операции (то есть AsyncStatus.Error). Конкретный код сбоя можно найти в свойстве ErrorCode асинхронной операции.

op.Completed = (info, status) =>
{
if (status == AsyncStatus.Completed)
{
SyndicationFeed feed = info.GetResults();
UpdateAppWithFeed(feed);
}
else if (status == AsyncStatus.Canceled)
{
// Operation canceled
}
else if (status == AsyncStatus.Error)
{
// Error occurred, Report error
}
};

Как это работает: сообщение о ходе выполнения с помощью асинхронных примитивов

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

В WinRT отчетность о ходе выполнения обрабатывается через интерфейсы IAsyncActionWithProgress<TProgress> и IAsyncOperationWithProgress<TResult, TProgress>. Каждый интерфейс содержит событие Progress, которое можно использовать для получения от асинхронной операции отчетов о ходе выполнения.

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

 

op = client.RetrieveFeedAsync(feedUri);

float percentComplete = 0;
op.Progress = (info, progress) =>
{
percentComplete = progress.BytesRetrieved /
(float)progress.TotalBytesToRetrieve;
};

В этом случае второй параметр (progress) имеет тип RetrievalProgress. Этот тип содержит два параметра (bytesRetrieved и totalBytesToRetrieve), которые используются для сообщения о текущем ходе выполнения.

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

Заключение

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

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

 

--Джейсон Олсон (Jason Olson)

Руководитель программы, Windows

  • Loading...
Leave a Comment
  • Please add 5 and 7 and type the answer here:
  • Post