Как реализовать новую функцию асинхронности в 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);
  }
}

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

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

3 ответа

Решение

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

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

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

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

/questions/8180607/c-zhdu-prodolzheniya-ne-sovsem-to-zhe-samoe/8180617#8180617

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

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

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

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

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

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

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


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

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

Другие вопросы по тегам