Подтвердить что ты не робот

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

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

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 в С# 5.0 вместо специального случая async/await, как выглядел бы этот сниппет?

4b9b3361

Ответ 1

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

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

Хорошо, сначала подумайте о том, что делает "ожидание". "Ожидание" принимает выражение типа Task<T>, получает awaiter и вызывает awaiter с текущим продолжением:

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.

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

Таким образом, связь между "ожидать" и "callcc" не так проста, как в предыдущем выпуске, но по-прежнему ясно, что мы по существу делаем callcc в методе "OnCompleted" для awaiter. Мы просто не делаем callcc, если нам это не нужно.


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

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

fooobar.com/questions/165031/...

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

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

Ответ 2

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

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

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

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

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


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

Ответ 3

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