Здравствуйте, уважаемыее!
В своей дебютной статье я хотел бы рассказать об использовании механизма async/await в C# 5.0 (и .NET Framework 4.5) при программировании для Windows Phone 7.x и 8.
Я занимаюсь программированием не только на работе, в последнее время я увлёкся разработкой под мобильные операционные системы и нахожу это весьма занимательным. А поскольку я давно погрузился в мир Microsoft, то первой и пока единственной платформой для меня стала Windows Phone.
Суть вопроса
На самом деле, с самого анонса C# 5.0 в далёком 2010 году было ясно, что механизм async/await — это то, что, попробовав однажды, программист захочет использовать везде, где это уместно. Действительно: ведь вместо того, чтобы писать порой достаточно сложный многопоточный код с последующей синхронизацией получившихся потоков, достаточно всего лишь использовать один из доступных async-методов или же обернуть уже существующий код в async.
Вся соль состоит в том, что если писать под Windows Phone, то по возможности (пока ещё) нужно поддерживать не только 8-ую версию, но и 7.5/7.8 — это как минимум правило хорошего тона, а вообще разработчик от этого только выигрывает. Но Windows Phone 7.x «из коробки» не поддерживает async/await, а это значит, что либо у вас получится огромное количество асинхронного кода, написанного «традиционными» средствами, либо выйдет что-то наподобие некоторых имеющихся в Магазине приложений, запускающихся столько времени, сколько уйдёт у смартфона на загрузку контента. И то, и другое — определённо не есть хорошо.
Решение
Решить эту проблему призван NuGet-пакет Microsoft.Bcl.Async, первая версия которого появилась ещё в октябре 2012 г., а финальный релиз состоялся всего около недели назад. Пакет поддерживает не только Windows Phone, но и «взрослый» .NET Framework 4.0 (с обновлением KB2468871, справедливости ради), Silverlight 4 и 5, ну и соответствующие Portable Class Libraries. Использовать его можно только в Visual Studio 2012 — видимо, реализация поддержки ключевых слов async/await в 2010-й «студии» себя не оправдала, по крайней мере в глазах Microsoft.
Кстати, что странно: на Хабре про этот пакет я нашёл совсем немного упоминаний.
Дальше идёт несколько выжимок из официального анонса релиза Microsoft.Bcl.Async в блоге BCL.
Во-первых, Microsoft.Bcl.Async полностью заменяет собой выпускавшийся ранее «Async Targeting Pack» (Microsoft.CompilerServices.AsyncTargetingPack
). Не в том смысле, что Async Targeting Pack обновится через NuGet до Bcl.Async, а в плане прекращения его разработки и настойчивой рекомендации заменить его на новый пакет. В принципе, такое обновление проходит безболезненно.
Во-вторых, для установки Microsoft.Bcl.Async понадобится версия NuGet 2.1 или выше. Обновляется через менеджер расширений Visual Studio и не вызывает никаких проблем. Просто для информации: если NuGet ругается, проверьте его версию.
В-третьих, из-за использования механизма унификации сборок, все сборки, использующиеся функциональность async/await, должны содержать ссылки на необходимые пакеты (Microsoft.Bcl.Async, Microsoft.Bcl и Microsoft.Bcl.Build). В финальной версии пакета разработчики добавили соответствующее предупреждение, возникающее в процессе компиляции решения, так что на это стоит обратить внимание.
Использование
Использование пакета Microsoft.Bcl.Async в среде Windows Phone 7.5/7.8 несильно отличается от async/await в .NET 4.5. Правда, всё же есть некоторые оговорки: например, статические функции класса Task
из .NET 4.5 в пакете Bcl.Async перекочевали в класс TaskEx
, а необходимые для поддержки асинхронности классы из пространства имён System.Runtime.CompilerServices
— в Microsoft.Runtime.CompilerServices
.
Версия Microsoft.Bcl.Async для Windows Phone 7.5/7.8 содержит все необходимые для async/await API, в то время, как версия для Windows Phone 8 только расширяет границы сознания имеющиеся возможности.
Поэтому я для себя решил, что в проектах Windows Phone 8 пакет Microsoft.Bcl.Async мне не нужен. Тем не менее, в оригинальном WP8 SDK отсутствуют асинхронные методы некоторых классов, например, WebRequest
. И хотя Microsoft не предоставляет исходных кодов Bcl.Async, внутренности библиотек можно легко просмотреть любым декомпилятором. Так что я создал небольшой класс TaskAsyncExtensions
в своём проекте, куда добавил некоторые методы расширения, например, такие:
public static Task<Stream> GetRequestStreamAsync(this WebRequest source)
{
return Task.Factory.StartNew(
() => Task<Stream>.Factory.FromAsync(source.BeginGetRequestStream, source.EndGetRequestStream, null),
CancellationToken.None,
TaskCreationOptions.None,
TaskScheduler.Default)
.Unwrap();
}
public static Task<WebResponse> GetResponseAsync(this WebRequest source)
{
return Task.Factory.StartNew(
() => Task<WebResponse>.Factory.FromAsync(source.BeginGetResponse, source.EndGetResponse, null),
CancellationToken.None,
TaskCreationOptions.None,
TaskScheduler.Default)
.Unwrap();
}
К слову сказать, идея с Task.Factory.StartNew(...).Unwrap()
появилась именно в финальной версии пакета Microsoft.Bcl.Async: в RC-версии, вышедшей в конце января, эти методы возвращали просто Task.Factory.FromAsync(...)
. Может, кто-нибудь пояснит в комментариях, в чём принципиальное отличие?
Заключение
Всё это, на самом деле, позволяет использовать имеющиеся наработки с наименьшими «твиками» на разных платформах. Например, один из основных классов в моём приложении, работающих с сетью, содержит только один «твик» для кроссплатформенности:
#if WP7
string requestParams = await Task.Factory.StartNew(() => JsonConvert.SerializeObject(jParams));
#else
string requestParams = await JsonConvert.SerializeObjectAsync(jParams);
#endif
Предвещая вопросы о директивах препроцессора и portable-библиотеках, скажу следующее: я стараюсь делать так, чтобы .XAP-пакеты моих приложений занимали как можно меньше места, а сами приложения использовали как можно меньше ссылок. Может, это бзик, хотя с размером пакетов объяснение простое — их качают по сотовой сети. Однако же в данном случае я вполне могу использовать Microsoft.Bcl.Async со всеми его зависимостями в проекте Windows Phone 7.x и не использовать его совсем в проекте Windows Phone 8. Мне так спокойнее.
Я очень надеюсь, что эта статья кому-то поможет и, может, даже станет откровением в плане существования Microsoft.Bcl.Async, а также я очень рассчитываю на то, что вы не станете пренебрегать такой замечательной возможностью, как async/await, и все приложения, загружающие данные из Интернета в процессе запуска, в скором времени канут в Лету. Спасибо за внимание!
Материалы:
- Блог разработчиков Base Class Library (BCL): blogs.msdn.com/b/bclteam
Автор: SgtRiggs91