Как реализовать новую функцию асинхронности в c # 5.0 с помощью call / cc? - PullRequest
19 голосов
/ 01 ноября 2010

Я следил за новым объявлением о новой функции async, которая будет в c # 5.0. У меня есть общее представление о стиле передачи продолжения и о преобразовании, которое новый компилятор c # делает в коде, подобном этому фрагменту из сообщения Эрика Липперта :

async void ArchiveDocuments(List<Url> urls)
{
  Task archive = null;
  for(int i = 0; i < urls.Count; ++i)
  {
    var document = await FetchAsync(urls[i]);
    if (archive != null)
      await archive;
    archive = ArchiveAsync(document);
  }
}

Я знаю, что некоторые языки реализуют продолжения изначально, через call-with-current-continue (callcc), но я не совсем понимаю, как это работает или что именно делает.

Итак, вот вопрос: Андерс и соавт. решил прикусить пулю и просто внедрить callcc в c # 5.0 вместо особого случая async / await, как будет выглядеть приведенный выше фрагмент кода?

Ответы [ 3 ]

28 голосов
/ 01 ноября 2010

Оригинальный ответ:

Ваш вопрос, насколько я понимаю, заключается в том, "что, если вместо реализации" ожидания "специально для асинхронности на основе задач, скорее, была реализована более общая операция потока управления вызовом с текущим продолжением?"

Ну, во-первых, давайте подумаем о том, что делает "ожидание". «await» принимает выражение типа Task<T>, получает ожидающего и вызывает ожидающего с текущим продолжением:

await FooAsync()

становится эффективно

var task = FooAsync();
var awaiter = task.GetAwaiter();
awaiter.BeginAwait(somehow get the current continuation);

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

var task = FooAsync();
var awaiter = task.GetAwaiter();
callcc awaiter.BeginAwait;

Другими словами:

await FooAsync()

не более чем

callcc FooAsync().GetAwaiter().BeginAwait;

Это отвечает на ваш вопрос?


Обновление № 1:

Как указывает комментатор, ответ ниже предполагает шаблон генерации кода из версии «Предварительный просмотр технологии» функции async / await. Мы на самом деле генерируем немного другой код в бета-версии функции, хотя логически это то же самое. Нынешний кодоген выглядит примерно так:

var task = FooAsync();
var awaiter = task.GetAwaiter();
if (!awaiter.IsCompleted)
{
    awaiter.OnCompleted(somehow get the current continuation);
    // control now returns to the caller; when the task is complete control resumes...
}
// ... here:
result = awaiter.GetResult();
// And now the task builder for the current method is updated with the result.

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

Таким образом, связь между «await» и «callcc» не так проста, как это было в предварительном выпуске, но все еще ясно, что мы по сути делаем callcc для метода «OnCompleted» ожидающего. Мы просто не делаем callcc, если не обязаны.


Обновление № 2:

Как этот ответ

https://stackoverflow.com/a/9826822/88656

от Тимви указывает, что семантика вызовов / cc и await не совсем одинакова; «истинный» вызов / cc требует либо «захвата» полного продолжения метода , включая весь стек вызовов , либо, что то же самое, чтобы вся программа была переписана в стиль передачи продолжения .

Функция "ожидание" больше похожа на "совместный вызов / cc"; продолжение содержит только то, «какой текущий метод возврата задачи собирается делать дальше в точке ожидания?» Если вызывающий метода возврата задачи собирается сделать что-то интересное после завершения задачи, тогда он может бесплатно зарегистрировать его продолжение как продолжение задачи.

4 голосов
/ 16 декабря 2010

Я не эксперт в продолжениях, но попробую объяснить разницу между async / await и call / cc.Конечно, это объяснение предполагает, что я понимаю call / cc и async / await, что я не уверен, что понимаю.Тем не менее, здесь идет речь ...

С помощью C # 'async' вы говорите компилятору генерировать специальную версию этого конкретного метода, который понимает, как поместить его состояние в структуру данных кучи, поэтомуможет быть "удален из реального стека" и возобновлен позже.Внутри асинхронного контекста «await» тогда похож на «call / cc» в том смысле, что он использует объекты, сгенерированные компилятором, для ограничения состояния и выхода из «реального стека» до завершения задачи.Однако, поскольку переписывание асинхронного метода компилятором позволяет ограничивать состояние, await можно использовать только в контексте асинхронности.

В вызове первого класса / cc среда выполнения языка генерирует всекод такой, что текущее продолжение может быть заключено в функцию продолжения вызова (что делает ненужным ключевое слово async).call / cc по-прежнему действует как ожидание, в результате чего текущее состояние продолжения (думайте о состоянии стека) будет загружено и передано как функция вызываемой функции.Один из способов сделать это - использовать кадры кучи для вызова функции all вместо стековых фреймов.(иногда называемый «стеком без стека», как в «Python без стека» или во многих реализациях схемы). Другой способ - удалить все данные из «реального стека» и поместить их в структуру данных кучи перед вызовом цели call / cc.,

Некоторые хитрые проблемы могут возникнуть, если в стек смешаны вызовы внешних функций (например, DllImport).Я подозреваю, что это причина, по которой они пошли с реализацией async / await.

http://www.madore.org/~david/computers/callcc.html


Поскольку в C # функция должна быть помечена как "асинхронная", чтобы использовать эту механику, мне интересно, станет ли это ключевое слово async распространяющимся вирусоммного функций во многих библиотеках.Если это произойдет.в конечном итоге они могут понять, что должны реализовать первоклассный вызов / cc на уровне виртуальной машины вместо этой асинхронной модели, основанной на перезаписи компилятора.Время покажет.Тем не менее, это, безусловно, полезный инструмент в контексте текущей среды C #.

3 голосов
/ 01 ноября 2010

Вроде бессмысленно, я бы сказал. Интерпретаторы схемы часто реализуют вызов / cc с помощью конечного автомата, который фиксирует локальное состояние в куче. Это точно , что делает C # 5.0 (или, вернее, итератор C # 2.0). Они сделали реализовали call / cc, абстракция, которую они придумали, довольно элегантна.

...