Как новая асинхронная функция в c# 5.0 может быть реализована с помощью call / cc?

Я следил за новым объявлением относительно нового 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-continuation (callcc), но я не очень понимаю, как это работает или что он делает именно так.

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

3 ответов


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

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

Ну, прежде всего давайте подумаем о том, что "ждут" ли. "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

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

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


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

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

в первоклассном вызове/cc языковая среда выполнения генерирует весь код таким образом, что текущее продолжение может быть закупорено в call-continuation-function (делает ключевое слово async ненужным). call / cc по-прежнему действует как await, вызывая текущее состояние продолжения (подумайте о состоянии стека), которое будет нарушено и передано как функция вызываемой функции. Один из способов сделать это-использовать heap-frames для все вызов функции вместо кадров "стека". (иногда упоминается как "stackless", как в "Stackless python" или многих реализациях схемы) другой способ-удалить все данные из " реального стек " и вставьте его в структуру данных кучи перед вызовом целевого объекта call/cc.

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

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


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


Я бы сказал, бессмысленно. Интерпретаторы схемы часто реализуют вызов / cc с машиной состояния, которая захватывает локальное состояние в кучу. Которая составляет ровно что делает C# 5.0 (или, точнее, итератор C# 2.0). Они!--1-->сделал реализовать вызов / cc, абстракция, которую они придумали, довольно элегантна.