С# Async - Как это работает?

Сегодня Microsoft анонсировала Visual Studio Async CTP (28 октября 2010 г.), которая вводит ключевые слова async и await в С# VB для выполнения асинхронного метода.

Сначала я подумал, что компилятор переводит ключевые слова в создание потока, но в соответствии с белой бумагой и Anders Hejlsberg PDC-презентация (в 31:00) асинхронная операция происходит полностью в основном потоке.

Как я могу выполнить операцию, выполняемую параллельно в одном потоке? Как это технически возможно и какова функция, фактически переведенная в ИЛ?

Ответ 1

Он работает аналогично ключевому слову yield return в С# 2.0.

Асинхронный метод на самом деле не является обычным последовательным методом. Он скомпилирован в конечный автомат (объект) с некоторым состоянием (локальные переменные превращаются в поля объекта). Каждый блок кода между двумя применениями await является одним "шагом" конечного автомата.

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

async Task Demo() { 
  var v1 = foo();
  var v2 = await bar();
  more(v1, v2);
}

Будет переведено на что-то вроде:

class _Demo {
  int _v1, _v2;
  int _state = 0; 
  Task<int> _await1;
  public void Step() {
    switch(this._state) {
    case 0: 
      this._v1 = foo();
      this._await1 = bar();
      // When the async operation completes, it will call this method
      this._state = 1;
      op.SetContinuation(Step);
    case 1:
      this._v2 = this._await1.Result; // Get the result of the operation
      more(this._v1, this._v2);
  }
}

Важная часть состоит в том, что он просто использует метод SetContinuation, чтобы указать, что, когда операция завершается, он должен снова вызвать метод Step (и метод знает, что он должен запустить второй бит исходного кода, используя поле _state). Вы легко можете себе представить, что SetContinuation будет чем-то вроде btn.Click += Step, который будет работать полностью на одном потоке.

Модель асинхронного программирования в С# очень близка к асинхронным рабочим процессам F # (на самом деле это, по сути, одно и то же, кроме некоторых технических деталей), и писать реактивные однопоточные графические приложения с использованием async довольно интересно область - по крайней мере, я так думаю - см., например, в этой статье (возможно, мне стоит написать версию С#: -)).

Перевод похож на итераторы (и yield return), и на самом деле было возможно использовать итераторы для реализации асинхронного программирования на С# раньше. Я написал статью об этом некоторое время назад - и я думаю, что он все равно может дать вам некоторое представление о том, как работает перевод.

Ответ 2

Как я могу выполнить операцию, выполняемую параллельно в одном потоке?

Вы не можете. Асинхронность не "parallelism" или "concurrency" . Асинхронность может быть реализована с помощью parallelism, или может быть и не так. Это может быть реализовано путем разбивки работы на небольшие куски, помещения каждого фрагмента работы в очередь и последующего выполнения каждого фрагмента работы, когда нить не делает ничего другого.

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

http://blogs.msdn.com/b/ericlippert/archive/tags/async/

для деталей.

Ответ 3

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

В соответствии с имеющимися документами есть много подробностей, но, если я не ошибаюсь, это суть.

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