Domanda

Cosa sono e a cosa servono?

Non ho una laurea in informatica e il mio background è VB6 -> ASP -> ASP.NET/C#.Qualcuno riesce a spiegarlo in modo chiaro e conciso?

È stato utile?

Soluzione

Immagina se ogni singola riga del tuo programma fosse una funzione separata.Ciascuno accetta, come parametro, la riga/funzione successiva da eseguire.

Usando questo modello, puoi "mettere in pausa" l'esecuzione su qualsiasi riga e continuarla in seguito.Puoi anche fare cose creative come saltare temporaneamente allo stack di esecuzione per recuperare un valore o salvare lo stato di esecuzione corrente in un database per recuperarlo in seguito.

Altri suggerimenti

Probabilmente li capisci meglio di quanto pensi di aver fatto.

Le eccezioni sono un esempio di continuazioni "solo verso l'alto".Consentono al codice in profondità nello stack di richiamare un gestore di eccezioni per indicare un problema.

Esempio 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

I generatori sono esempi di continuazioni "solo verso il basso".Consentono al codice di rientrare in un ciclo, ad esempio, per creare nuovi valori.

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

In entrambi i casi, questi dovevano essere aggiunti specificatamente al linguaggio, mentre in un linguaggio con continuazioni, il programmatore può creare queste cose dove non sono disponibili.

Attenzione: questo esempio non è conciso né eccezionalmente chiaro.Questa è una dimostrazione di una potente applicazione delle continuazioni.Come programmatore VB/ASP/C#, potresti non avere familiarità con il concetto di stack di sistema o stato di salvataggio, quindi l'obiettivo di questa risposta è una dimostrazione e non una spiegazione.

Le continuazioni sono estremamente versatili e rappresentano un modo per salvare lo stato di esecuzione e riprenderlo in un secondo momento.Ecco un piccolo esempio di un ambiente multithreading cooperativo che utilizza le continuazioni in Scheme:

(Supponiamo che le operazioni di accodamento e rimozione dalla coda funzionino come previsto su una coda globale non definita qui)

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

Ciò fornisce tre verbi che una funzione può utilizzare: fork, cambio contesto e processo finale.L'operazione fork biforca il thread e restituisce #t in un'istanza e #f in un'altra.L'operazione di cambio di contesto passa da un thread all'altro e il processo finale termina un thread.

Ecco un esempio del loro utilizzo:

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

L'output dovrebbe essere

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)

A coloro che hanno studiato fork e threading in un corso vengono spesso forniti esempi simili a questo.Lo scopo di questo post è dimostrare che con le continuazioni è possibile ottenere risultati simili all'interno di un singolo thread salvando e ripristinando manualmente il suo stato, ovvero la sua continuazione.

PS- Penso di ricordare qualcosa di simile in On Lisp, quindi se desideri vedere il codice professionale dovresti dare un'occhiata al libro.

Un modo di pensare a una continuazione è come uno stack di processori.Quando "chiama con continuazione corrente c" chiama la tua funzione "c" e il parametro passato a "c" è il tuo stack corrente con tutte le variabili automatiche su di esso (rappresentato come un'altra funzione, chiamalo "k ").Nel frattempo il processore inizia a creare un nuovo stack.Quando chiami "k" esegue un'istruzione "return from subroutine" (RTS) sullo stack originale, riportandoti nel contesto della "call-with-current-continuation" originale ("call-cc" da adesso on) e consentendo al programma di continuare come prima.Se hai passato un parametro a "k", questo diventa il valore restituito da "call-cc".

Dal punto di vista dello stack originale, "call-cc" sembra una normale chiamata di funzione.Dal punto di vista di "c", il tuo stack originale sembra una funzione che non ritorna mai.

C'è una vecchia battuta su un matematico che catturò un leone in gabbia entrando nella gabbia, chiudendola a chiave e dichiarando di essere fuori dalla gabbia mentre tutto il resto (compreso il leone) era al suo interno.Le continuazioni sono un po' come la gabbia, e la "c" è un po' come il matematico.Il tuo programma principale pensa che "c" sia al suo interno, mentre "c" crede che il tuo programma principale sia all'interno di "k".

È possibile creare strutture di flusso di controllo arbitrarie utilizzando le continuazioni.Ad esempio puoi creare una libreria di threading."yield" usa "call-cc" per mettere la continuazione corrente in una coda e poi passa a quella in testa alla coda.Un semaforo ha anche una propria coda di continuazioni sospese e un thread viene riprogrammato togliendolo dalla coda del semaforo e inserendolo nella coda principale.

Fondamentalmente, una continuazione è la capacità di una funzione di interrompere l'esecuzione e poi riprendere da dove era stata interrotta in un secondo momento.In C#, puoi farlo utilizzando la parola chiave yield.Se vuoi posso entrare più nel dettaglio, ma volevi una spiegazione concisa.;-)

Mi sto ancora "abituando" alle continuazioni, ma un modo di pensarle che trovo utile è come astrazioni del concetto di Program Counter (PC).Un PC "punta" alla successiva istruzione da eseguire in memoria, ma ovviamente quell'istruzione (e praticamente ogni istruzione) punta, implicitamente o esplicitamente, all'istruzione che segue, così come a qualsiasi istruzione dovrebbe servire gli interrupt.(Anche un'istruzione NOOP esegue implicitamente un SALTO all'istruzione successiva in memoria.Ma se si verifica un'interruzione, di solito ciò comporterà un SALTO a qualche altra istruzione in memoria.)

Ogni punto potenzialmente "vivo" in un programma in memoria a cui il controllo potrebbe passare in un dato punto è, in un certo senso, una continuazione attiva.Altri punti che possono essere raggiunti sono continuazioni potenzialmente attive, ma, più precisamente, sono continuazioni potenzialmente "calcolate" (dinamicamente, forse) come risultato del raggiungimento di una o più continuazioni attualmente attive.

Ciò sembra un po' fuori luogo nelle tradizionali introduzioni alle continuazioni, in cui tutti i thread di esecuzione in sospeso sono esplicitamente rappresentati come continuazioni nel codice statico;ma tiene conto del fatto che, sui computer generici, il PC punta a una sequenza di istruzioni che potrebbe potenzialmente modificare il contenuto della memoria che rappresenta una porzione di quella sequenza di istruzioni, creando così essenzialmente una nuova (o modificata, se si vuole). ) continuazione al volo, che in realtà non esiste a partire dalle attivazioni di continuazioni precedenti quella creazione/modifica.

Quindi la continuazione può essere vista come un modello di alto livello del PC, motivo per cui concettualmente sussume la procedura ordinaria di chiamata/ritorno (proprio come l'antico ferro faceva la procedura di chiamata/ritorno tramite JUMP di basso livello, ovvero GOTO, istruzioni più registrazione del PC in chiamata e ripristino al ritorno), nonché eccezioni, thread, coroutine, ecc.

Quindi, proprio come il PC indica che i calcoli avverranno nel "futuro", una continuazione fa la stessa cosa, ma a un livello più alto e più astratto.Il PC si riferisce implicitamente alla memoria più tutte le locazioni di memoria e i registri "legati" a qualunque valore, mentre una continuazione rappresenta il futuro attraverso le astrazioni appropriate al linguaggio.

Naturalmente, anche se in genere potrebbe esserci un solo PC per computer (core processore), in realtà ci sono molte entità "attive" simili a quelle dei PC, come accennato in precedenza.Il vettore di interruzione ne contiene un mucchio, lo stack un mucchio di più, alcuni registri potrebbero contenerne alcuni, ecc.Vengono "attivati" quando i loro valori vengono caricati nell'hardware del PC, ma le continuazioni sono astrazioni del concetto, non PC o il loro esatto equivalente (non esiste un concetto intrinseco di continuazione "master", anche se spesso pensiamo e codifichiamo in questi termini per mantenere le cose abbastanza semplici).

In sostanza, una continuazione è una rappresentazione di "cosa fare dopo quando viene invocata" e, come tale, può essere (e, in alcuni linguaggi e in programmi in stile passaggio di continuazione, spesso lo è) un oggetto di prima classe che è istanziati, passati e scartati proprio come la maggior parte degli altri tipi di dati e in modo molto simile al modo in cui un computer classico tratta le posizioni di memoria di fronte il PC - quasi intercambiabile con i normali numeri interi.

In C# hai accesso a due continuazioni.Uno, accessibile tramite return, consente al metodo di continuare da dove è stato chiamato.L'altro, accessibile tramite throw, consente al metodo di continuare con la corrispondenza più vicina catch.

Alcuni linguaggi ti consentono di trattare queste istruzioni come valori di prima classe, quindi puoi assegnarli e passarli in variabili.Ciò significa che puoi nascondere il valore di return o di throw e chiamali più tardi quando vuoi Veramente pronto per restituire o lanciare.

Continuation callback = return;
callMeLater(callback);

Questo può essere utile in molte situazioni.Un esempio è come quello sopra, in cui vuoi mettere in pausa il lavoro che stai facendo e riprenderlo più tardi quando succede qualcosa (come ricevere una richiesta web o qualcosa del genere).

Li sto usando in un paio di progetti a cui sto lavorando.In uno, li utilizzo in modo da poter sospendere il programma mentre aspetto l'I/O sulla rete, per poi riprenderlo in seguito.Nell'altro, sto scrivendo un linguaggio di programmazione in cui do all'utente l'accesso alle continuazioni come valori in modo che possano scrivere return E throw per se stessi - o qualsiasi altro flusso di controllo, come while loop - senza che io debba farlo per loro.

Pensa ai thread.È possibile eseguire un thread e ottenere il risultato del suo calcolo.Una continuazione è un thread che puoi copiare, in modo da poter eseguire lo stesso calcolo due volte.

Le continuazioni hanno avuto un rinnovato interesse per la programmazione web perché rispecchiano perfettamente il carattere di pausa/ripresa delle richieste web.Un server può costruire una continuazione che rappresenta la sessione di un utente e riprenderla se e quando l'utente continua la sessione.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top