2012-03-22 2 views
11

После прочтения Eric Lippert’s answer У меня сложилось впечатление, что await и call/cc - это почти две стороны одной и той же монеты, с большинством синтаксических различий. Однако, пытаясь на самом деле реализовать call/cc в C# 5, у меня возникла проблема: либо я неправильно понимаю call/cc (что вполне возможно), либо ждет только reminiscent call/cc.C# ждут продолжения: не совсем то же самое?

Рассмотрим псевдокод так:

function main: 
    foo(); 
    print "Done" 

function foo: 
    var result = call/cc(bar); 
    print "Result: " + result; 

function bar(continuation): 
    print "Before" 
    continuation("stuff"); 
    print "After" 

Если мое понимание вызова/куб.см правильно, то это должно напечатать:

Before 
Result: stuff 
Done 

Кардинально, когда продолжение называется, программа состояние восстановлено вместе с историей звонков, так что foo возвращается в main и никогда не возвращается к bar.

Однако, если реализовано с использованием await в C#, вызов продолжения не восстановить эту историю звонков. foo возвращается в bar, и нет способа (я могу видеть), что await может использоваться, чтобы сделать правильную историю вызовов частью продолжения.

Пожалуйста, объясните: я полностью неправильно понял операцию call/cc или await просто не совсем то же самое, что и call/cc?

Теперь, когда я знаю ответ, я должен сказать, что есть веская причина думать о них как о довольно похожей. Рассмотрим, что выше программа выглядит в псевдо-C# -5:

function main: 
    foo(); 
    print "Done" 

async function foo: 
    var result = await(bar); 
    print "Result: " + result; 

async function bar(): 
    print "Before" 
    return "stuff"; 
    print "After" 

Так в то время как стиль C# 5, никогда не дает нам объект продолжения, чтобы передать значение, общее сходство весьма впечатляющая. За исключением того, что на этот раз совершенно очевидно, что «After» никогда не вызывается, в отличие от примера с истинным вызовом/cc, что является еще одной причиной любить C# и хвалить его дизайн!

+0

Timwi правильный; ожидание больше похоже на вызов «local effect»/cc; это тонкость, о которой я не думал кричать в своем первоначальном ответе. Я обновил его. –

ответ

18

await действительно не совсем то же самое, что и call/cc.

Тип полностью фундаментального call/cc, о котором вы думаете, действительно должен был бы сохранить и восстановить весь стек вызовов. Но await - это просто трансформация времени компиляции. Он делает что-то подобное, но не использует real стек вызовов.

Представьте, что вы имеете функцию асинхронной, содержащую ждут выражение:

async Task<int> GetInt() 
{ 
    var intermediate = await DoSomething(); 
    return calculation(intermediate); 
} 

Теперь представьте себе, что функция, которую вы звоните через awaitсам содержит await выражение:

async Task<int> DoSomething() 
{ 
    var important = await DoSomethingImportant(); 
    return un(important); 
} 

Теперь подумайте о том, что происходит, когда DoSomethingImportant() заканчивается и его результат доступен. Управление возвращается к DoSomething(). Затем DoSomething() заканчивается и что происходит потом?Управление возвращается к GetInt(). Поведение в точности равно будет, если GetInt() были в стеке вызовов. Но на самом деле это не так; вы должны использовать await по адресу каждый, который вы хотите имитировать таким образом. Таким образом, стек вызовов поднимается в стек мета-вызовов, который реализуется в awaiter.

То же самое, кстати, относится и к yield return:

IEnumerable<int> GetInts() 
{ 
    foreach (var str in GetStrings()) 
     yield return computation(str); 
} 

IEnumerable<string> GetStrings() 
{ 
    foreach (var stuff in GetStuffs()) 
     yield return computation(stuff); 
} 

Теперь, если я называю GetInts(), что я получаю назад это объект, который инкапсулирует текущее состояние выполнения GetInts() (так что вызов MoveNext() на это резюме когда он остановился). Этот объект сам содержит итератор, который выполняет итерацию через GetStrings() и вызывает MoveNext() на , что. Таким образом, стек вызовов real заменяется иерархией объектов, которые каждый раз воссоздают правильный стек вызовов с помощью серии вызовов MoveNext() на следующем внутреннем объекте.

+0

Кстати, при отладке асинхронизации вы видите MoveNext как имя метода в точке останова. Это говорит о том, что они использовали генератор машины итератора из IEnumerable. –

Смежные вопросы