2008-09-02 91 views
32

它们是什么,它们有什么用途?我只是没有得到延续!

我没有一个CS学位,我的背景是VB6 - > ASP - > ASP.NET/C#。任何人都可以用清晰简洁的方式解释它吗?

回答

41

试想一下,如果在你的程序每一行是一个单独的函数。每个接受作为参数的下一行/函数来执行。

使用这个模型,你可以“暂停”的执行在任何线并在稍后继续它。你也可以做一些有创意的事情,比如暂时跳出执行堆栈来检索一个值,或者将当前的执行状态保存到数据库中以便稍后检索。

+0

哇,这实际上是有道理的。谢谢! – minimalpop 2009-12-03 03:02:56

9

抬起头来,这个例子并不简明,也非常清楚。这是一个强大的延续应用的演示。作为VB/ASP/C#程序员,您可能不熟悉系统堆栈或保存状态的概念,因此此答案的目标仅仅是演示而不是解释。

延续用途极其广泛,是一种保存的执行状态,并在稍后继续。下面是使用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。上下文切换操作在线程之间切换,并且结束进程终止线程。

这里是他们使用的例子:

(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中记得类似这样的东西,所以如果你想看专业代码,你应该检查一下这本书。

4

基本上,延续到停止执行并然后再爬起来它在以后的时间点离开的地方的能力的函数。在C#中,您可以使用yield关键字来完成此操作。如果你愿意的话,我可以详细介绍一下,但你想要一个简洁的解释。 ;-)

2

我仍然得到“拿来主义”,以延续,但一个方法来思考他们,我觉得有用的程序计数器(PC)的概念的抽象。 PC“指向”要在内存中执行的下一条指令,但当然,该指令(以及几乎每条指令)都会隐式或显式指向后面的指令,以及任何应该中断服务的指令。 (即使是NOOP指令也会隐式地执行内存中下一条指令的JUMP,但是如果发生中断,那么通常会涉及到内存中某些其他指令的JUMP。)

程序中的每个潜在“活动”点在任何给定点控制可能跳到的记忆中,在某种意义上说是积极的延续。其他可以达到的点是潜在的主动延续,但更重要的是,它们是可能“计算”的延续(可能是动态的),作为达到一个或多个当前有效延续的结果。

这似乎有点出在传统的介绍给延续,其中执行的所有未决线程被明确地表示为延续到静态代码位;但是它考虑到了这样一个事实,即在通用计算机上,PC指向一个指令序列,该指令序列可能会改变表示该指令序列的一部分的存储器的内容,从而基本上创建一个新的(或修改过的,如果你愿意的话) )在飞行中的延续,这种延续并非真正存在于创建/修改之前的延续激活。

所以延续可以被看作是PC的一个高级模型,这就是为什么它在概念上包含了普通的过程调用/返回(就像古铁过程调用/通过低级JUMP,又名GOTO,返回指令加上PC上的电话录音和恢复时的恢复)以及例外,线程,协同程序等。

所以就像PC指向在“未来”中发生的计算一样,延续也是一样的的东西,但在更高,更抽象的层面。个人电脑隐含地指内存加上所有内存位置和寄存器“绑定”到任何值,而后续通过与语言相关的抽象表示未来。

当然,虽然可能存在通常只是一个每计算机(核心处理器)PC,有事实上许多“活性” PC-ISH实体,如上面所提到的。中断向量包含一堆,堆栈多一些,某些寄存器可能包含一些等等。当它们的值被加载到硬件PC时,它们“被激活”,但延续是概念的抽象,而不是PC或它们的精确等价(没有“主”延续的固有概念,尽管我们经常认为并用这些术语编码来使事情保持简单)。

实质上,延续是“被调用时接下来做什么”的表示,并且因此可以是(并且在一些语言中并且在延续传递式程序中通常是)被实例化,传来传去,并丢弃就像大多数其他任何数据类型的类对象,就像如何一个典型的计算机将内存位置面对面的人的PC - 作为几乎与普通的整数互换。

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() 

在这两种情况下,这些必须被添加到语言明确,而与延续语言,程序员可以创建这些东西,他们是不可用的。

7

思考延续的一种方法是作为处理器堆栈。当你用“call-with-current-continuation c”调用你的函数“c”时,传递给“c”的参数就是你当前的栈,其上有所有的自动变量(表示为另一个函数,称之为“k “)。同时处理器开始创建一个新的堆栈。当您调用“k”时,它会在原始堆栈上执行“从子程序返回”(RTS)指令,将您跳回原来的“call-with-current-continuation”(从现在起的“call-cc”上),并让您的程序像以前一样继续。如果您将参数传递给“k”,则这将成为“call-cc”的返回值。

从原始堆栈的角度来看,“call-cc”看起来像是一个正常的函数调用。从“c”的角度来看,你的原始堆栈看起来像一个永不返回的函数。

对于一个数学家来说,有一个古老的笑话,他通过爬进笼子里,抓住一只笼子里的狮子并锁定它,并宣称自己在笼子外面,而其他所有物体(包括狮子)都在笼子里面。延续有点像笼子,“c”有点像数学家。你的主程序认为“c”在里面,而“c”则认为你的主程序在“k”里面。

您可以使用continuation创建任意的控制流结构。例如,你可以创建一个线程库。 “yield”使用“call-cc”将当前的继续放在队列中,然后跳转到队列头上的那个。信号量也有其自己的挂起延续队列,并且通过将线程从信号量队列中取出并将其放到主队列中来重新安排线程。

2

在C#中,您可以访问两个继续。一个通过return访问,让一个方法继续它被调用的地方。另一个通过throw访问,让方法继续在最接近的匹配catch

有些语言让您将这些语句视为一等价值,因此您可以将它们分配并在变量中传递它们。这意味着你可以隐藏returnthrow的值,并在以后打电话给真的准备返回或抛出。

Continuation callback = return; 
callMeLater(callback); 

这可以在很多情况下得心应手。一个例子就像上面的例子,你想暂停你正在做的工作,并在事情发生的时候(比如获取Web请求等)恢复它。

我正在使用他们在我正在处理的几个项目中。其中之一是我正在使用它们,因此我可以在通过网络等待IO时暂停程序,然后再恢复。另一方面,我正在编写一种编程语言,让用户可以访问continuations-as-values,这样他们就可以为自己编写returnthrow - 或者任何其他控制流,例如while循环 - 而无需为此执行他们。

1

想想线程。一个线程可以运行,并且你可以得到它的计算结果。延续是您可以复制的线程,因此您可以运行相同的计算两次。

1

继续对Web编程重新产生兴趣,因为它们很好地反映了Web请求的暂停/恢复特性。服务器可以构造代表用户会话的连续性,并在用户继续会话时恢复。