它们是什么以及它们有什么好处?

我没有 CS 学位,我的背景是 VB6 -> ASP -> ASP.NET/C#。谁能以清晰简洁的方式解释它?

有帮助吗?

解决方案

想象一下,如果程序中的每一行都是一个单独的函数。每个都接受要执行的下一行/函数作为参数。

使用此模型,您可以在任何行“暂停”执行并稍后继续执行。您还可以做一些有创意的事情,例如临时跳转执行堆栈以检索值,或者将当前执行状态保存到数据库以便稍后检索。

其他提示

您可能比您想象的更了解它们。

例外是“仅向上”延续的一个示例。它们允许堆栈深处的代码调用异常处理程序来指示问题。

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

在这两种情况下,这些都必须专门添加到语言中,而在具有延续的语言中,程序员可以在它们不可用的地方创建这些东西。

请注意,这个示例既不简洁也不特别清晰。这是延续的强大应用的演示。作为一名 VB/ASP/C# 程序员,您可能不熟悉系统堆栈或保存状态的概念,因此本答案的目的是演示而不是解释。

延续的用途非常广泛,是保存执行状态并稍后恢复的一种方法。下面是一个在Scheme中使用延续的协作多线程环境的小例子:

(假设入队和出队操作在此处未定义的全局队列上按预期工作)

(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、上下文切换和结束进程。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)

那些在课堂上研究过分叉和线程的人经常会得到与此类似的示例。这篇文章的目的是证明,使用延续,您可以通过手动保存和恢复其状态(其延续)在单个线程中实现类似的结果。

附:- 我想我记得《On Lisp》中有类似的内容,所以如果你想看到专业代码,你应该看看这本书。

将延续视为处理器堆栈的一种方法。当您“call-with-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”里面。

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

基本上,延续是函数停止执行,然后在稍后的时间点从中断处恢复的能力。在 C# 中,您可以使用 Yield 关键字来执行此操作。如果您愿意,我可以提供更多详细信息,但您想要一个简洁的解释。;-)

我仍然“习惯”延续,但我发现有用的一种思考它们的方法是将其作为程序计数器(PC)概念的抽象。PC“指向”内存中要执行的下一条指令,但当然该指令(以及几乎每条指令)隐式或显式地指向后面的指令,以及应该服务中断的任何指令。(即使是 NOOP 指令也会隐式跳转到内存中的下一条指令。但如果发生中断,通常会涉及跳转到内存中的其他指令。)

从某种意义上说,内存中程序中控制可能在任何给定点跳转到的每个潜在“活动”点都是活动延续。可以到达的其他点是潜在的活动延续,但更重要的是,它们是由于到达一个或多个当前活动的延续而可能(动态地)“计算”的延续。

这在传统的延续介绍中似乎有点不合适,其中所有挂起的执行线程都显式表示为静态代码的延续;但它考虑到这样一个事实:在通用计算机上,PC 指向一个指令序列,该指令序列可能会更改代表该指令序列一部分的内存内容,从而本质上创建一个新的(或修改过的,如果您愿意的话) ) 动态延续,在创建/修改之前激活延续时并不真正存在。

因此,延续可以被视为 PC 的高级模型,这就是为什么它在概念上包含普通的过程调用/返回(就像古代铁通过低级 JUMP(又名 GOTO)进行过程调用/返回一样,指令加上记录调用时的 PC 并在返回时恢复它)以及异常、线程、协程等。

因此,正如 PC 指向“未来”发生的计算一样,延续也做同样的事情,但在更高、更抽象的层面上。PC 隐式引用内存加上“绑定”到任何值的所有内存位置和寄存器,而延续则通过适合语言的抽象来表示未来。

当然,虽然每台计算机通常只有一台 PC(核心处理器),但实际上存在许多“活跃的”PC 型实体,如上所述。中断向量包含一堆,堆栈更多,某些寄存器可能包含一些,等等。当它们的值加载到硬件 PC 中时,它们就会被“激活”,但延续是概念的抽象,而不是 PC 或其精确的等价物(没有“主”延续的固有概念,尽管我们经常用这些术语来思考和编码让事情变得相当简单)。

本质上,延续是“调用时下一步做什么”的表示,因此,可以是(并且在某些语言和延续传递风格的程序中,通常是)一个一流的对象,它是就像大多数其他数据类型一样实例化、传递和丢弃,并且非常类似于经典计算机处理内存位置的方式 相对 PC——几乎可以与普通整数互换。

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

有些语言允许您将这些语句视为一流值,因此您可以分配它们并在变量中传递它们。这意味着您可以隐藏以下值 return 或的 throw 稍后当你在的时候给他们打电话 真的 准备返回或抛出。

Continuation callback = return;
callMeLater(callback);

这在很多情况下都很方便。一个示例类似于上面的示例,您希望暂停正在执行的工作,并在发生某些情况(例如收到 Web 请求等)时恢复它。

我在我正在进行的几个项目中使用它们。其中之一是,我使用它们,这样我就可以在等待网络上的 IO 时暂停程序,然后稍后再恢复它。另一方面,我正在编写一种编程语言,让用户可以访问延续作为值,以便他们可以编写 returnthrow 对于他们自己 - 或任何其他控制流,例如 while 循环 - 不需要我为他们做这件事。

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

Continuation 重新引起了人们对 Web 编程的兴趣,因为它们很好地反映了 Web 请求的暂停/恢复特征。服务器可以构建表示用户会话的延续,并在用户继续会话时恢复。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top