Asynchronous 如何实现看似同步的异步代码,模仿async/await?

Asynchronous 如何实现看似同步的异步代码,模仿async/await?,asynchronous,networking,scheme,chez-scheme,Asynchronous,Networking,Scheme,Chez Scheme,否则,我想依靠epoll(或类似)来编写异步网络代码,它看起来像不依赖回调的常规代码 代码必须看起来像同步代码,但与同步代码不同的是,它没有阻塞等待网络io,而是必须挂起当前的协同程序,并在文件描述符准备好时重新启动它。我最初的想法是依靠生成器和生成。但这在一定程度上是错误的,因为python过去常常滥用从中获得的收益 不管怎么说,guile纤维是一个很好的灵感来源 以下是一个示例服务器代码: (define (handler request port) (values 200 #f (ht

否则,我想依靠
epoll
(或类似)来编写异步网络代码,它看起来像不依赖回调的常规代码


代码必须看起来像同步代码,但与同步代码不同的是,它没有阻塞等待网络io,而是必须挂起当前的协同程序,并在文件描述符准备好时重新启动它。

我最初的想法是依靠生成器和
生成
。但这在一定程度上是错误的,因为python过去常常滥用
中获得的收益

不管怎么说,guile纤维是一个很好的灵感来源

以下是一个示例服务器代码:

(define (handler request port)
  (values 200 #f (http-get "https://httpbin.davecheney.com/ip")))

(untangle (lambda ()
            (run-server "127.0.0.1" 8888)))
处理程序
根据httpbin服务返回其IP。代码在call/cc/1cc的帮助下看起来是同步的

untangle
将使用作为参数传递的lambda启动事件循环

以下是运行服务器的定义:

(define (run-server ip port handler)
  (log 'info "HTTP server running at ~a:~a" ip port)
  (let* ((sock (socket 'inet 'stream 'ipv4)))
    (socket:setsockopt sock 1 2 1) ;; re-use address
    (socket:bind sock (make-address ip port))
    (socket:listen sock 1024)
    (let loop ()
      (let ((client (accept sock)))
        (let ((port (fd->port client)))
          (spawn (lambda () (run-once handler port)))
          (loop))))))
正如您所看到的,没有回调。与简单同步Web服务器不同的唯一一点是
spawn
过程,它将在自己的协同程序中处理请求。尤其是
accept
是异步的

运行一次
将仅将方案请求传递给
处理程序
,并获取其3个值以构建响应。不太有趣。看起来是同步的,但实际上是异步的部分是上面的
httpget

我将只解释accept是如何工作的,因为http get需要引入自定义二进制端口,但只需说它是相同的行为

(define (accept fd)
  (let ((out (socket:%accept fd 0 0)))
    (if (= out -1)
        (let ((code (socket:errno)))
          (if (= code EWOULDBLOCK)
              (begin
                (abort-to-prompt fd 'read)
                (accept fd))
              (error 'accept (socket:strerror code))))
        out)))
如您所见,它调用一个过程
abort来提示
,我们可以简单地调用该过程
pause
,该过程将“停止”协同程序并调用提示处理程序

abort to prompt
call with prompt
协同工作

由于chez方案没有提示,所以我使用两个单发连续体来模拟它

调用并提示
将启动一个
集合的延续全局调用了
%prompt
,这意味着对
THUNK
有一个提示。如果延续参数
OUT
,则带有值的
调用的第二个lambda以唯一对象
%abort
开始,这意味着延续是通过
abort到达的,以提示
。它将调用
处理程序
,使用
中止提示
继续,并使用提示
继续参数调用传递给
的任何参数(应用处理程序(cons k(cdr out))

abort to promp
将在代码执行存储在
%prompt
中的提示继续后,启动一个新的继续,以便能够返回

带有提示符的
调用是事件循环的核心。这是它,分为两部分:

(define (exec epoll thunk waiting)
  (call-with-prompt
   thunk
   (lambda (k fd mode) ;; k is abort-to-prompt continuation that
                       ;; will allow to restart the coroutine

     ;; add fd to the correct epoll set
     (case mode
       ((write) (epoll-wait-write epoll fd))
       ((read) (epoll-wait-read epoll fd))
       (else (error 'untangle "mode not supported" mode)))
     (scheme:hash-table-set! waiting fd (make-event k mode)))))

(define (event-loop-run-once epoll waiting)
  ;; execute every callback waiting in queue, 
  ;; call the above exec procedure 
  (let loop ()
    (unless (null? %queue)
      ;; XXX: This is done like that because, exec might spawn
      ;; new coroutine, so we need to cut %queue right now. 
      (let ((head (car %queue))
            (tail (cdr %queue)))
        (set! %queue tail)
        (exec epoll head waiting)
        (loop))))

    ;; wait for ONE event
    (let ((fd (epoll-wait-one epoll (inf))
      (let ((event (scheme:hash-table-ref waiting fd)))
        ;; the event is / will be processed, no need to keep around
        (scheme:hash-table-delete! waiting fd)
        (case (event-mode event)
          ((write) (epoll-ctl epoll 2 fd (make-epoll-event-out fd)))
          ((read) (epoll-ctl epoll 2 fd (make-epoll-event-in fd))))
        ;; here it will schedule the event continuation that is the
        ;; abort-to-prompt continuation that will be executed by the
        ;; next call the above event loop event-loop-run-once
        (spawn (event-continuation event))))))

我想就这些了。

如果你用的是chez方案,就有了。它使用POSIX轮询而不是epoll(epoll是特定于linux的)。guile-a-sync2也可用于guile-2.2/3.0

(define (exec epoll thunk waiting)
  (call-with-prompt
   thunk
   (lambda (k fd mode) ;; k is abort-to-prompt continuation that
                       ;; will allow to restart the coroutine

     ;; add fd to the correct epoll set
     (case mode
       ((write) (epoll-wait-write epoll fd))
       ((read) (epoll-wait-read epoll fd))
       (else (error 'untangle "mode not supported" mode)))
     (scheme:hash-table-set! waiting fd (make-event k mode)))))

(define (event-loop-run-once epoll waiting)
  ;; execute every callback waiting in queue, 
  ;; call the above exec procedure 
  (let loop ()
    (unless (null? %queue)
      ;; XXX: This is done like that because, exec might spawn
      ;; new coroutine, so we need to cut %queue right now. 
      (let ((head (car %queue))
            (tail (cdr %queue)))
        (set! %queue tail)
        (exec epoll head waiting)
        (loop))))

    ;; wait for ONE event
    (let ((fd (epoll-wait-one epoll (inf))
      (let ((event (scheme:hash-table-ref waiting fd)))
        ;; the event is / will be processed, no need to keep around
        (scheme:hash-table-delete! waiting fd)
        (case (event-mode event)
          ((write) (epoll-ctl epoll 2 fd (make-epoll-event-out fd)))
          ((read) (epoll-ctl epoll 2 fd (make-epoll-event-in fd))))
        ;; here it will schedule the event continuation that is the
        ;; abort-to-prompt continuation that will be executed by the
        ;; next call the above event loop event-loop-run-once
        (spawn (event-continuation event))))))