我只是没有继续!

时间:2020-03-05 18:46:44  来源:igfitidea点击:

它们是什么,它们有什么用?

我没有计算机科学学位,我的背景是VB6-> ASP-> ASP.NET/C#。谁能以简洁明了的方式解释它?

解决方案

回答

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

使用此模型,我们可以在任何行"暂停"执行并在以后继续执行。我们还可以做一些创造性的事情,例如临时跳上执行堆栈以检索值,或者将当前执行状态保存到数据库中以供以后检索。

回答

请注意,该示例并不简洁也不十分清楚。这证明了延续的强大应用。作为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操作将线程分叉,并在一个实例中返回#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》中与此类似的内容,因此,如果我们想查看专业代码,则应将其签出。

回答

基本上,延续是一种功能,该功能可以停止执行,然后在以后的某个时间点停下来的地方重新执行。在C#中,我们可以使用yield关键字执行此操作。如果我们愿意,我可以更详细地介绍,但是我们想要一个简洁的解释。 ;-)

回答

我仍然可以继续使用"继续使用",但是我认为有用的一种思考方式是对程序计数器(PC)概念的抽象。 PC"指向"要在内存中执行的下一条指令,但是当然,该指令(以及几乎每条指令)都隐式或者显式地指向随后的指令以及应该处理中断的任何指令。 (即使NOOP指令也隐式地对内存中的下一条指令执行JUMP。但是,如果发生中断,通常会将JUMP传递至内存中的其他指令。)

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

在传统的延续介绍中,这似乎有点不合时宜,在这种介绍中,所有未决的执行线程都明确表示为静态代码的延续。但考虑到以下事实:在通用计算机上,PC指向一个指令序列,该指令序列可能会更改代表该指令序列一部分的内存内容,从而从本质上创建一个新的(或者修改的,如果我们愿意)动态续播,在创建/修改之前激活续播时实际上并不存在。

因此,延续性可以看作是PC的高级模型,这就是为什么它在概念上包含普通过程调用/返回的原因(就像古老的铁通过低级JUMP,aka GOTO,指令以及记录记录来完成过程调用/返回一样)。 PC处于调用状态并在返回时恢复它),以及异常,线程,协程等。

因此,正如PC指出计算将在"未来"中发生一样,延续可以做同样的事情,但要更高,更抽象。 PC隐式地指代内存加上所有内存位置,并"绑定"到任何值的寄存器,而延续则通过适合语言的抽象表示未来。

当然,尽管通常每台计算机(核心处理器)可能只有一台PC,但实际上有许多"活跃的" PC类实体,如上所述。中断向量包含一堆,堆栈中有更多堆,某些寄存器可能包含一些,依此类推。当将它们的值加载到硬件PC中时,它们将被"激活",但延续是概念的抽象,而不是PC或者其精确等效物(尽管我们经常以这些术语进行思考和编码以使事情相当简单,但是没有"主"延续的固有概念)。

本质上,延续是"被调用时下一步要做的事情"的表示,因此,延续可以是(并且在某些语言中以及在延续传递样式的程序中通常是)一流对象,即就像大多数其他数据类型一样,实例化,传递和丢弃,就像经典计算机对待PC相对于PC的内存位置一样,几乎可以与普通整数互换。

回答

我们可能比我们认为的要了解它们。

例外是"仅向上"延续的示例。它们允许代码深入堆栈中,以调用异常处理程序来指示问题。

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

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

回答

一种考虑延续性的方法是将其视为处理器堆栈。当我们"用当前连续调用c"调用它的函数" c"时,传递给" c"的参数是当前堆栈,上面带有所有自动变量(表示为另一个函数,称为" k" ")。同时,处理器开始创建新的堆栈。当我们调用" k"时,它将在原始堆栈上执行"从子例程返回"(RTS)指令,从而使我们跳回到原始" call-with-current-continuation"(从现在开始为" call-cc"上),并允许程序像以前一样继续。如果将参数传递给" k",则该参数将成为" call-cc"的返回值。

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

有个关于数学家的古老笑话,他是通过爬进笼子,锁住狮子并宣布自己在笼子里而其他所有东西(包括狮子)都在笼子里来捕获狮子的。延展有点像笼子," c"有点像数学家。主程序认为" c"在其中,而" c"认为主程序在" k"内。

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

回答

在C#中,我们可以访问两个延续。一个通过return访问的方法,使方法从被调用的地方继续。另一个通过" throw"访问,使方法在最接近的匹配" catch"处继续。

某些语言允许我们将这些语句视为一流的值,因此我们可以分配它们并将它们传递给变量。这意味着我们可以存储" return"或者" throw"的值,并在真正准备好返回或者抛出时稍后调用它们。

Continuation callback = return;
callMeLater(callback);

在许多情况下这可能很方便。一个示例类似于上面的示例,我们想在其中暂停正在执行的工作,然后在发生某些情况(例如获取Web请求或者其他内容)时恢复它。

我正在我正在进行的几个项目中使用它们。在其中之一中,我正在使用它们,因此我可以在等待网络上的IO时挂起程序,然后稍后再恢复。在另一种情况下,我正在编写一种编程语言,使用户可以访问作为值的continuation-as-value,以便他们可以自己或者任何其他控制流编写" return"和" throw",例如" while"循环而无需我为他们做。

回答

想想线程。线程可以运行,我们可以获得其计算结果。连续是可以复制的线程,因此我们可以运行两次相同的计算。

回答

连续性已经引起了人们对Web编程的兴趣,因为它们很好地反映了Web请求的暂停/恢复特性。服务器可以构造代表用户会话的续集,并在用户继续会话时以及何时继续恢复。