Вопрос

Что это такое и для чего они хороши?

У меня нет степени 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:

(Предположим, что операции enqueue и dequeue работают должным образом в глобальной очереди, не определенной здесь)

(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, так что, если вы хотите увидеть профессиональный код, вам следует ознакомиться с книгой.

Один из способов представить продолжение - это использовать процессорный стек. Когда вы "звоните с текущим продолжением c" она вызывает вашу функцию "c" и параметр, переданный в "c" это ваш текущий стек со всеми вашими автоматическими переменными (представлен как еще одна функция, назовите ее "k"). Тем временем процессор начинает создавать новый стек. Когда вы звоните " k " он выполняет «возврат из подпрограммы» (RTS) инструкция в исходном стеке, возвращая вас обратно в контекст оригинального «call-with-current-продолжением» (" call-cc " с этого момента) и разрешить продолжить выполнение вашей программы, как раньше. Если вы передали параметр " k " тогда это становится возвращаемым значением " call-cc ".

С точки зрения вашего исходного стека, " call-cc " выглядит как обычный вызов функции. С точки зрения " c " ваш исходный стек выглядит как функция, которая никогда не возвращается.

Есть старая шутка о математике, который поймал льва в клетке, забравшись в клетку, заперев ее и объявив себя вне клетки, пока все остальное (включая льва) было внутри нее. Продолжения немного похожи на клетку, и «с» это немного похоже на математику. Ваша основная программа считает, что " c " находится внутри него, в то время как "c" считает, что ваша основная программа находится внутри " k ".

Вы можете создавать произвольные структуры потока управления, используя продолжения. Например, вы можете создать библиотеку потоков. & Quot; выход & Quot; использует " call-cc " поместить текущее продолжение в очередь, а затем перейти к тому, которое находится в начале очереди. Семафор также имеет свою собственную очередь приостановленных продолжений, и поток перепланируется путем удаления его из очереди семафора и помещения его в основную очередь.

По сути, продолжение - это возможность для функции остановить выполнение и затем вернуться туда, где она остановилась в более поздний момент времени. В C # вы можете сделать это, используя ключевое слово yield. Я могу вдаваться в подробности, если вы хотите, но вы хотели краткое объяснение. ; -)

Я все еще использую " использованный " продолжения, но один способ думать о них, который я считаю полезным, - это абстракции концепции счетчика программ (ПК). ПК "очки" к следующей инструкции, выполняемой в памяти, но, конечно, эта инструкция (и почти каждая инструкция) указывает, неявно или явно, на следующую инструкцию, а также на любые инструкции, которые должны обслуживать прерывания. (Даже инструкция NOOP неявно выполняет JUMP для следующей инструкции в памяти. Но если происходит прерывание, это обычно включает JUMP для какой-либо другой инструкции в памяти.)

Каждый потенциально "живой" Точка в программе в памяти, к которой может перейти элемент управления в любой заданной точке, является в некотором смысле активным продолжением. Другие точки, которые могут быть достигнуты, являются потенциально активными продолжениями, но, более конкретно, они являются продолжениями, которые потенциально "рассчитываются". (возможно, динамически) в результате достижения одного или нескольких активных в настоящее время продолжений.

Это кажется немного неуместным в традиционных введениях в продолжения, в которых все ожидающие потоки выполнения явно представлены как продолжения в статическом коде; но он принимает во внимание тот факт, что на компьютерах общего назначения ПК указывает на последовательность команд, которая потенциально может изменить содержимое памяти, представляющее часть этой последовательности команд, таким образом, по существу, создавая новую (или измененную, если вы будете ) продолжение на лету, которое на самом деле не существует по состоянию активаций продолжений, предшествующих этому созданию / модификации.

Таким образом, продолжение можно рассматривать как высокоуровневую модель ПК, поэтому концептуально оно включает в себя обычный вызов / возврат процедуры (так же, как древнее железо выполняло вызов / возврат процедуры через низкоуровневый JUMP, или GOTO, инструкции и т.д.). запись ПК по вызову и его восстановление по возвращении), а также исключения, потоки, сопрограммы и т. д.

Таким образом, как ПК указывает на то, что вычисления произойдут в «будущем», продолжение делает то же самое, но на более высоком, более абстрактном уровне. ПК неявно ссылается на память плюс все ячейки памяти и регистры «связаны» к любым значениям, в то время как продолжение представляет будущее через соответствующие абстракции языка.

Конечно, хотя на каждом компьютере (основном процессоре) обычно может быть только один ПК, на самом деле их много "активных". Объекты PC-ish, как упоминалось выше. Вектор прерывания содержит группу, стек больше, некоторые регистры могут содержать некоторые и т. Д. Они "активированы" когда их значения загружаются в аппаратный ПК, но продолжения являются абстракцией концепции, а не ПК или их точным эквивалентом (не существует присущей концепции «главного» продолжения, хотя мы часто думаем и кодируем в этих терминах, чтобы сохранять справедливость вещей простой).

По сути, продолжение представляет собой представление «что делать дальше при вызове», и, как таковое, может быть (а в некоторых языках и в программах стиля передачи продолжения часто) первым Объект класса, который создается, передается и отбрасывается точно так же, как и большинство других типов данных, и очень похож на то, как классический компьютер рассматривает места в памяти vis-a-vis ПК - как почти взаимозаменяемые с обычными целые числа.

В C # у вас есть доступ к двум продолжениям. Одна, доступ к которой осуществляется через return , позволяет методу продолжить с того места, где он был вызван. Другой, доступ к которому осуществляется через throw , позволяет методу продолжать работу с ближайшего соответствующего catch .

Некоторые языки позволяют вам рассматривать эти операторы как первоклассные значения, поэтому вы можете назначать их и передавать их в переменных. Это означает, что вы можете спрятать значение return или throw и вызывать их позже, когда вы действительно готовы вернуться или бросить .

Continuation callback = return;
callMeLater(callback);

Это может быть полезно во многих ситуациях. Один пример похож на приведенный выше, где вы хотите приостановить выполняемую работу и возобновить ее позже, когда что-то случится (например, получение веб-запроса или что-то в этом роде).

Я использую их в нескольких проектах, над которыми я работаю. Во-первых, я использую их, чтобы я мог приостановить программу, ожидая ввода-вывода по сети, а затем возобновить ее позже. В другом я пишу язык программирования, где я даю пользователю доступ к продолжениям как значениям, чтобы они могли писать return и throw для себя - или любой другой поток управления, например, while - без необходимости делать это за них.

Подумайте о темах. Поток может быть запущен, и вы можете получить результат его вычисления. Продолжение - это поток, который вы можете скопировать, чтобы вы могли выполнить одно и то же вычисление дважды.

Продолжения возобновили интерес к веб-программированию, потому что они хорошо отражают характер паузы / возобновления веб-запросов. Сервер может создать продолжение, представляющее сеанс пользователя, и возобновить его, если и когда пользователь продолжит сеанс.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top