Главная Контакты Архив

C# 5.0. Новые возможности асинхронного программирования

Автор Сергей Шебанин 15 января 2011 15:14

20 ноября 2010 года на регулярной встрече тульских .NET разработчиков состоялся доклад Сергея Шебанина "C# 5.0. Взгляд в будущее".

На текущий момент .NET Framework 5.0 находится в состоянии разработки. На текущий момент одна из планируемых технологий опубликована в формате CTP (Community Technology Preview). О ней и пойдет рассказ.

Вступление

Язык формирует наш способ мышления и определяет то, о чем мы можем мыслить.
Б.Л. Ворф

Для того, чтобы лучше понять внутреннюю мотивацию разработчиков платформы .NET, нужно посмотреть на историю развития языка C#.

  • Все началось с C# 1.0.
    Основным нововведением в котором был Managed code. Теперь программистам не надо было думать об освободении памяти. Платформа автоматически делала это за них. Соответственно программисты могли лучше концентрироваться на логической задаче при написании кода и не отвлекались на вспомогательный код освобождения памяти. 
  • Потом пришел C# 2.0.
    Нововведениями в котором были итераторы, generics (параметризированные классы) и анонимные функции.
  • Следом пришел C# 3.0.
    Вместе с ним программисты узнали о LINQ и Lambda-выражениях.
  • Потом C# 4.0.
    А вместе с ним dynamic типы и PLINQ.
  • В C# 5.0 же нас ждет TAP (Task-based asynchronous pattern)

Зачем нужна асинхронность?

Самой распространенной операцией, которая занимает много непроцессорных ресурсов, является получение данных из сети. Давайте рассмотрим пример, в котором мы получаем документ из сети, а затем архивируем его.

var document = FetchDocument(url);
ArchiveDocument(document);

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

В асинхронном примере:

FetchDocumentAsync(url, document => {
    ArchiveDocument(document);
});

Процессор ведет себя уже по другому. Вместо ожидания он освобождается и может выполнять другие задачи.

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

FetchDocumentAsync(url1, document => {
    ArchiveDocument(document);
});
FetchDocumentAsync(url2, document => {
    ArchiveDocument(document);
});
FetchDocumentAsync(url3, document => {
    ArchiveDocument(document);
});

Асинхронность против многопоточности

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

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

Если посмотреть на области применимости асинхронного подхода, то это:

  • В первую очередь обработка событий пользовательского интерфейса
  • Скриптовые языки, такие как SilverLight (JavaScript, например, — язык вообще не поддерживающий многопоточность)
  • Программирование мультиплексоров и аналогичных им алгоритмов, использующих медленные внешние ресурсы: сеть, диски, другие накопители.

Сопрограммы

Чтобы понять суть нововведений в C# 5.0 давайте рассмотрим сначала сопрограммы.

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

Давайте рассмотрим пример программы из wikipedia, иллюстрирующий суть сопрограмм:

var q := new queue
coroutine produce
    loop while q is not full
        create some new items
        add the items to q
    yield to consume
coroutine consume
    loop while q is not empty
        remove some items from q
	use the items
    yield to produce

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

Что же нас ждет в C# 5.0

Возьмем синхронный код:

var document = FetchDocument(url);
ArchiveDocument(document);


Его можно преобразовать в асинхронный формат:

FetchDocumentAsync(url, document => {
    ArchiveDocument(document);
});

Или так, используя вспомогательный контейнер Task:

Task task = FetchDocumentAsync(url);
task.ContinueWith(document => {
	ArchiveDocument(document);
});

В C# 5.0 для этого мы можем использовать другой синтаксис:

ArchiveDocument(await FetchDocumentAsync(url));

Первое нововведение C# 5.0:

await task

Означает эта конструкция буквально следующее:

Если таск, для которого выполняется await еще не завершен, то установить остаток текущего метода как продолжение (continuation) таска, затем вернуть управление вызывающему методу.
Продолжение (continuation) метода будет вызвано таском, когда он будет завершен.
Конструкция await может быть указана только внутри async метода.

Второе нововведение C# 5.0:

void async SomeMethod() {}

Модификатор async означает буквально следующее:

Этот метод содержит конструкции await, так что компилятор должен преобразовать его таким образом, чтобы асинхронные операции могли продолжить выполнение этого метода. При этом метод может быть продолжен не с произвольной точки, а только с тех точек, которые запоминают конструкции await.
Кроме того методы async могут возвращать только void, Task или Task<T>. При этом void и Task методы не могут возвращать ничего внутри своей реализации. То есть содержать только return;
Task<T> методы могут внутри содержать только конструкцию return <something of type T>;

Примеры

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

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

 

Выкладываю работающий пример для VisualStudio 2010 с установленным C# Async CTP.

TAPTest.zip (11,73 kb)

Что еще важно знать (если Вы собираетесь применять TAP)

  • Конструкция await применима не только для Task. Но и для любого выражения, для которого определен метод GetAwaiter(), возвращающий тип, для которого можно вызвать методы BeginAwait(Action) и EndAwait()
  • Асинхронный синтаксис сохраняет интуитивно понятный способ обработки исключений для вашего кода
  • Предварительная версия компилятора C# 5.0 пока еще совсем предварительная. То есть в ней может поменяться все, вплоть до синтаксиса

Источники информации

  • http://msdn.com/vstudio/async
    Здесь можно скачать предварительную версию Async CTP (Community Technology Preview)
    Есть ссылки на документацию, примеры и обсуждения в блогах
    Много видео с выступлениями дизайнеров C# 5.0
  • http://nsentinel.blogspot.com/
    Можно подробно прочитать о TAP в C# 5.0 по-русски

Комментарии (1) -

Oleg
Oleg
25 марта 2011 12:46 #

Спасибо за инфу.
Жаль только с MVC3 несовместим Frown

Ответить

Добавить комментарий



biuquote
  • Комментарий
  • Предпросмотр
Loading