它们是什么,它们有什么用?
我没有CS学位,我的背景是VB6 - > ASP - > ASP.NET/C#。任何人都能以清晰简洁的方式解释它吗?
答案 0 :(得分:41)
想象一下,如果程序中的每一行都是一个单独的函数。每个接受作为参数的下一行/要执行的函数。
使用此模型,您可以在任何行“暂停”执行并稍后继续执行。您还可以执行创造性操作,例如暂时跳过执行堆栈以检索值,或将当前执行状态保存到数据库以便稍后检索。
答案 1 :(得分:11)
你可能比你想象的更好地理解它们。
例外是“仅向上”延续的一个例子。它们允许代码内部的代码调用异常处理程序来指示问题。
Python示例:
try:
broken_function()
except SomeException:
# jump to here
pass
def broken_function():
raise SomeException() # go back up the stack
# stuff that won't be evaluated
生成器是“向下”延续的例子。它们允许代码重新输入循环,例如,创建新值。
Python示例:
def sequence_generator(i=1):
while True:
yield i # "return" this value, and come back here for the next
i = i + 1
g = sequence_generator()
while True:
print g.next()
在这两种情况下,这些都必须特别添加到语言中,而在具有延续的语言中,程序员可以创建这些不可用的东西。
答案 2 :(得分:9)
抬头,这个例子并不简洁,也不是特别清楚。这是对continuation的强大应用的演示。作为VB / ASP / C#程序员,您可能不熟悉系统堆栈或保存状态的概念,因此这个答案的目标是演示而不是解释。
Continuations非常通用,是一种保存执行状态并在以后恢复的方法。以下是使用Scheme中的continuation的协作多线程环境的一个小例子:
(假设操作在此处未定义的全局队列上按预期排队和出列工作)
(define (fork)
(display "forking\n")
(call-with-current-continuation
(lambda (cc)
(enqueue (lambda ()
(cc #f)))
(cc #t))))
(define (context-switch)
(display "context switching\n")
(call-with-current-continuation
(lambda (cc)
(enqueue
(lambda ()
(cc 'nothing)))
((dequeue)))))
(define (end-process)
(display "ending process\n")
(let ((proc (dequeue)))
(if (eq? proc 'queue-empty)
(display "all processes terminated\n")
(proc))))
这提供了一个函数可以使用的三个动词 - fork,context-switch和end-process。 fork操作分叉线程并在一个实例中返回#t而在另一个实例中返回#f。上下文切换操作在线程之间切换,而end-process终止线程。
以下是他们使用的一个例子:
(define (test-cs)
(display "entering test\n")
(cond
((fork) (cond
((fork) (display "process 1\n")
(context-switch)
(display "process 1 again\n"))
(else (display "process 2\n")
(end-process)
(display "you shouldn't see this (2)"))))
(else (cond ((fork) (display "process 3\n")
(display "process 3 again\n")
(context-switch))
(else (display "process 4\n")))))
(context-switch)
(display "ending process\n")
(end-process)
(display "process ended (should only see this once)\n"))
输出应为
entering test
forking
forking
process 1
context switching
forking
process 3
process 3 again
context switching
process 2
ending process
process 1 again
context switching
process 4
context switching
context switching
ending process
ending process
ending process
ending process
ending process
ending process
all processes terminated
process ended (should only see this once)
那些在课堂上学习分叉和穿线的人经常会给出类似的例子。这篇文章的目的是证明通过延续,您可以通过手动保存和恢复其状态 - 它的继续 - 在单个线程中获得类似的结果。
P.S。 - 我想在On Lisp中记得类似的东西,所以如果你想看专业代码,你应该看看这本书。
答案 3 :(得分:7)
考虑延续的一种方法是作为处理器堆栈。当你“使用current-continuation c调用”时,它调用你的函数“c”,传递给“c”的参数是你当前的堆栈,上面包含你所有的自动变量(表示为另一个函数,称之为“k”) “)。与此同时,处理器开始创建一个新的堆栈。当你调用“k”时,它会在原始堆栈上执行“从子程序返回”(RTS)指令,跳回原来的“call-with-current-continuation”(从现在起“call-cc”)的上下文on)并允许您的程序像以前一样继续。如果您将参数传递给“k”,那么这将成为“call-cc”的返回值。
从原始堆栈的角度来看,“call-cc”看起来像是普通的函数调用。从“c”的角度来看,你的原始堆栈看起来像一个永不返回的函数。
有一个关于数学家的老笑话,他抓住一只笼子里的狮子爬进笼子里,把它锁起来,宣布自己在笼子外面,而其他一切(包括狮子)都在里面。延续有点像笼子,“c”有点像数学家。你的主程序认为“c”在里面,而“c”认为你的主程序在“k”里面。
您可以使用continuation创建任意控制流结构。例如,您可以创建一个线程库。 “yield”使用“call-cc”将当前的continuation放在队列中,然后跳转到队列头部的那个。信号量也有自己的暂停连续队列,并通过将其从信号量队列中取出并将其放入主队列来重新安排线程。
答案 4 :(得分:4)
基本上,延续是一个函数停止执行的能力,然后在稍后的某个时间点回到它停止的位置。在C#中,您可以使用yield关键字执行此操作。如果你愿意,我可以详细介绍,但你想要一个简明的解释。 ; - )
答案 5 :(得分:2)
我仍然“习惯”延续,但是我觉得有用的一种思考方式就是程序计数器(PC)概念的抽象。 PC“指向”下一条在内存中执行的指令,但当然该指令(以及几乎所有指令)隐式或显式地指向后面的指令,以及任何指令应该服务中断。 (即使是NOOP指令也会隐式地对存储器中的下一条指令执行JUMP。但是如果发生中断,那通常会将JUMP包含在内存中的其他指令中。)
在某种意义上,控制可能在任何给定点跳跃的内存中程序中的每个潜在的“实时”点都是一个主动延续。可以达到的其他点是潜在的活跃延续,但更重要的是,它们是可能被“计算”(可能动态地)到达一个或多个当前活动延续的结果。
在传统的continuation介绍中,这似乎有些不合适,其中所有挂起的执行线程都明确表示为静态代码的延续;但它考虑到这样一个事实,即在通用计算机上,PC指向一个指令序列,该指令序列可能会改变代表该指令序列一部分的内存内容,从而基本上创建一个新的(或修改,如果你愿意的话) )动态继续,在创建/修改之前的继续激活中并不存在。
因此,延续可以被视为PC的高级模型,这就是为什么它在概念上包含普通过程调用/返回(就像古代铁通过低级JUMP程序调用/返回,也就是GOTO,指令加上在通话中记录PC并在返回时恢复它)以及异常,线程,协同程序等。
正如PC指向在“未来”中发生的计算一样,延续也会做同样的事情,但是在更高,更抽象的层面。 PC隐含地指内存加上所有内存位置和寄存器“绑定”到任何值,而延续通过适合语言的抽象来表示未来。
当然,虽然每台计算机(核心处理器)通常只有一台PC,但实际上有许多“活跃的”PC-ish实体,如上所述。中断向量包含一堆,堆栈更多,某些寄存器可能包含一些等等。当它们的值被加载到硬件PC时它们被“激活”,但是延续是概念的抽象,而不是PC或它们的精确等价物(虽然我们经常用这些术语来思考和编码,以保持相当简单),但没有“主”延续的固有概念。
从本质上讲,延续是“在调用时下一步该做什么”的表示,因此,可以(并且,在某些语言和延续传递式程序中,通常是)一流的像大多数其他数据类型一样被实例化,传递和丢弃的对象,就像经典计算机如何处理相对于 PC的内存位置 - 几乎可以与普通整数互换
答案 6 :(得分:2)
在C#中,您可以访问两个续点。其中一个通过return
访问,允许方法从调用它的位置继续。通过throw
访问的另一个方法允许方法在最近的匹配catch
处继续。
有些语言允许您将这些语句视为一等值,因此您可以分配它们并在变量中传递它们。这意味着您可以隐藏return
或throw
的值,并在您 准备好返回或投掷时稍后再调用它们。
Continuation callback = return;
callMeLater(callback);
这在很多情况下都很方便。一个例子就像上面的例子,你想要暂停你正在做的工作,并在事情发生后再恢复(比如获取网络请求或其他东西)。
我在我正在做的几个项目中使用它们。在一个,我正在使用它们,所以我可以在我等待网络上的IO时暂停程序,然后再恢复它。另一方面,我正在编写一种编程语言,我可以让用户访问continuation-as-values,这样他们就可以为自己编写return
和throw
- 或任何其他控制流,例如{{ 1}}循环 - 没有我需要为他们做。
答案 7 :(得分:1)
想想线程。可以运行一个线程,您可以获得其计算结果。延续是一个可以复制的线程,因此您可以运行两次相同的计算。
答案 8 :(得分:1)
Continuations对Web编程有了新的兴趣,因为它们很好地反映了Web请求的暂停/恢复特性。服务器可以构建表示用户会话的连续性,并在用户继续会话时恢复。