Jan Fan     About     Archive     Feed     English Blog

Event-Driven Model and Callback

对编程有了解的朋友对“回调”(Callback)这个概念一定不会陌生吧? 它在程序设计\框架中屡见不鲜。

它们也是一种通信(inter-process communication IPC)手段?

也许,我们还可以再谈谈“阻塞”(Blocking)。

如果你对这些概念的内涵和联系也和之前的我一样,像在偷窥——总看不仔细,那么就来看看这篇文章吧:)

Event-Driven Programming

传统的程序执行都是一条道走到黑的,能够自定义的就是程序执行前用户提供的个性化参数。

Traditionally, a program simply ran once then terminated. This type of program was very common in the early days of computing, and lacked any form of user interactivity. Any parameters are set up in advance and passed in one go when the program starts.

而事件驱动,则给程序带来了更多的“用户交互”(User Interactivity)的能力,这在兴起的图形化和Web程序中极为重要(想想你点击过多少的按钮和链接)。

In computer programming, event-driven programming or event-based programming is a programming paradigm in which the flow of the program is determined by events—i.e., sensor outputs or user actions (mouse clicks, key presses) or messages from other programs or threads.

Event-driven programming is the dominant paradigm used in graphical user interfaces and other applications (e.g. JavaScript web applications)

但程序如何在执行过程中捕捉突如其来的事件(Event)并对其进行响应呢? 这个过程需要解决两个问题。

  1. Event selection (or event detection), and
  2. Event handling.

下面我们来一个一个地解决。

Communication

在系统集成,最大的挑战就是如何在两个东西之间建立某种类型的通道,从而通信——实体A向实体B发出请求,然后收到应答。

解决方法——抽象层,也称为接口(Interface),是一种位于两个或多个实体间的软件成分,通过对实体间的输入和输出进行解释和路由,来取代它们之间的直接通信。 就像翻译人员那样,而在软件系统中,我们比较常见的中间人角色就莫过于操作系统了,它帮助完成进程间的通信\上层应用与下层硬件的通信等。

 +-------+
 |Chinese|
 |People |
 +-------+
     |
 +-----------+
 |Interpreter|
 +-----------+
     |
 +-------+
 |English|
 |People |
 +-------+

但是各实体与中间抽象层程序又是如何直接通信的呢? 在几乎所有软件系统中,两个实体间的接口通常是由一些公开的函数来实现——通过调用其中一个函数,本质上你就可以向某个实体“发送信息”。

  1. 当X调用了Y的一个函数,就是向Y发送了一个请求;
  2. 当Y返回一个值或调用了X的一个函数时,就作出了应答

这应答方式里的函数调用,正是“回调”(Callback)

callback

回调可以分成两类,区别主要在于回调函数被调用的时机——是当作参数或变量传到handler的时候马上被调用,还是先在handler中注册等待日后再调用。

  1. blocking callbacks (also known as synchronous callbacks or just callbacks), and
  2. deferred callbacks (also known as asynchronous callbacks).

These two design choices differ in how they control data flow at runtime. While blocking callbacks are invoked before a function returns (in the C example: main), deferred callbacks may be invoked after a function returns. The C example is a blocking callback. Deferred callbacks are often used in the context of I/O operations or event handling.

贴维基上面一个blocking callbacks的例子,非常直观,下文主要讨论deferred callbacks,它与事件驱动有着紧密的关系。

#include <stdio.h>
#include <stdlib.h>
 
/* The calling function takes a single callback as a parameter. */
void PrintTwoNumbers(int (*numberSource)(void)) {
    printf("%d and %d\n", numberSource(), numberSource());
}
 
/* A possible callback */
int overNineThousand(void) {
    return (rand() % 1000) + 9001;
}
 
/* Another possible callback. */
int meaningOfLife(void) {
    return 42;
}
 
/* Here we call PrintTwoNumbers() with three different callbacks. */
int main(void) {
    PrintTwoNumbers(&rand);
    PrintTwoNumbers(&overNineThousand);
    PrintTwoNumbers(&meaningOfLife);
    return 0;
}

Deferred Callback的实现

Deferred callback的实现可以分为两部分

  1. 注册(Registration)
  2. 调用(Invocation)

从C语言的角度来看,callback其实就是函数指针的传递。 对象A在对象B中注册了一个函数,B中的一个变量就保存了该函数的地址,然后在适当的时候适用这个函数。

         A
 +---------------+
 |func_callback()|
 +---------------+
    |        /\
    |        |
   addr   invoke
    |        |
    \/       |
 +---------------+
 |func_registry()|
 +---------------+
         B

关键就在第2部分,程序会在“适当的时候”再调用原先注册的函数。 可是,计算机怎么确定这个时机呢?等待(Event Loop)和事件分发(Event Dispatch)。

Event loop is a programming construct that waits for and dispatches events or messages in a program. It works by making a request to some internal or external “event provider” (which generally blocks the request until an event has arrived), and then it calls the relevant event handler (“dispatches the event”).

事件的来源大多来自其它进程的同步\I/O事件\中断和异常,因此event loop的阻塞不外乎是休眠或者busy waiting,如果采用异步事件或Unix Signal事件驱动,甚至不需要另开一个线程来执行event loop,只需要挂载一个handler,然后该干啥干啥去。

若来自其它进程,则可以参考IPC的通信同步手段,比如锁\I/O阻塞等。

若event来自I/O事件,则实现方法可以参考I/O模型,阻塞的方法分为以下几类

若来自底层的运行时异常,比如divide by 0,在Linux下则监听Unix Signal来处理。

在软件中,除了上面介绍的进程\线程间的事件触发,还有一种比较特殊的有关代码变量中的事件设定,在Urbiscript中有提供这样的脚本并发功能,比如

除了轮询之外,在代码中我们还可以采用封装,将对x的访问封装起来,比如只提供一个setter(int x_)外部函数来对其进行修改。

这样一来,一旦x的数值发生变化,setter()就会对新的值进行检查并伺机发起callback函数的回调。

常见的Callback的例子

这里是一些笔者自己根据过往经验回想起的一些例子,贴上来供大家与理论概念相联系。

以后凡是看到带handler字样的代码,马上就要想起callback的概念了啊:)

最后

Event-Driven Programming的两个问题

  1. 事件的检测(Event Detection):主要依靠新开一个进程\线程来轮询\阻塞来监听,或Unix Signal事件驱动的手段来解决,
  2. 事件的处理(Event Handling):简单地用函数指针就可以完成。

“看完这篇文章感觉怎么样?” “妈妈我也懂事件驱动了!”

主要参考资料

Comments

多说 Disqus