对回调函数和消息机制的理解

【概念理解及两者的区别】

无论是回调函数还是消息机制都是函数调用的方式。

对我们来说,函数调用就是我们认为在需要的时候调某个方法,这就是最常见的函数调用的方式——方法调用。

方法调用都是通过类名来调用某个方法,实现方法调用最关键的就是怎么找到这个类的引用。如果是静态类,或者单例类,或者静态方法,就很方便,否则我们需要通过各种各样的方式将这个类的引用传递过来。

我们用方法调用的时候是满足了这样的条件:我们需要在满足某个条件A的时候去调用某个函数B,而目前条件A刚好满足。(也可以说满足了调用函数B的条件A)

方法调用通常会不断循环,在一个方法里调用另一个方法。例如,在满足A时调用函数B获得结果C,又立刻根据结果C,如果结果C大于多少,去调用函数D,否则调用函数E。如果等待调用函数和函数调用者在统一类中呢?

例如,在A类的a1方法中,会调用B类的b1方法,A类持有B类引用,可以采用方法调用在a1中完成对b1的调用。而A类中的a2方法在满足某个条件时需要被调用,而恰恰在b1方法中这个条件会被满足。那么在b1方法中如何调用a2方法呢?

如果A类时静态类,或单例类,a2为静态方法,B类持有A类引用,那么直接通过方法调用即可。但总有例外的情况,我们也不想让A类成为静态或单例类,或者让a2为静态方法,或者让B类持有A类的引用,那么就需要在a1中调用(call)b1时,把a2作为参数传递给b1。a2就是回调函数。

对A类来说,B类中的b1调用了a2的方法,是在回调。但对B类来说,在b1中调a2的方法就是正常的方法调用,同样是满足了某个条件就调某个函数,只不过这个函数对A类来说是个回调函数。

回到函数解决了知道在满足某个条件的时候该调用什么函数,但不知道这个条件何时被满足时的问题。这是什么意思呢?

我们在a1中调用b1方法,b1里会去调其他方法,得到返回值,然后根据返回值调a2方法,在b1中我们只是知道了在满足某个条件时去调用a2,但不知道这个条件在a1调b1的过程中是否满足。有可能在其他地方调用b1的时候满足。

这就是函数调用的另一中方式,采用回调的形式调用某个函数。

可以认为回调函数在方法调用上包装了一层,是变相的方法调用,而消息是在回调函数的基础上包装了一层,是变相的回调函数。

在回调函数中将等待被调用方法a2传递了调用者b1,而在消息中,需要将a2传递给C,b1调用C的方法c1,然后c1调用a2。C就是消息的管理者,将a2传递给C换句话说就是注册某个消息,b1调用C的方法c1换句话说就是发送某个消息。无论是注册还是发送某个消息,都可以持有C的引用,进行方法调用。

可以看到,消息机制将回调函数中的调用者和被调用者隔离开了,调用者在消息机制中被称为消息发送者,被调用者被称为消息接收者。仅仅是这一个简单的改变,就会造成以下区别:

  1. 在消息中,由于被隔离了,所以发送消息者不知道谁接收了消息,消息接收者不知道谁发送了消息,耦合度降低了。(想知道也可以,只要在注册和发送消息时添加格外的参数就行了。)在回调中,可以明确知道是是谁调用,谁被调用。
  2. 消息有管理者,管理者需要管理很多消息的发送和接受,也即管理满足不同条件时的方法调用,而回调函数只是满足某一个条件时的方法调用。
  3. 一个消息可以有多个发送者,多个接收者;而在回调中,调用者和被调用者往往只有一个。如果一个消息只有一个发送者和一个接收者,那么这实际就是一个回调。更多的情况是一个发送者和多个接收者,发送者在发消息时可以附带不同的参数,而消息接收者在收到消息后再去检查参数看是否响应消息,也即检查是否满足接下来函数调用的条件。因而回调有着更严格的参数控制,消息的参数更为宽松。
  4. 消息发送者可以决定什么时候发消息给消息管理者,但什么时候消息发给接收者是由消息管理者决定的,一般而言,消息管理者不是立马就发,而是再下一帧发给所有消息接收者。因而,消息是异步的。而在回调中,调用者可以立即调用被调用者,因而回调,是同步的。
  5. 消息可以跨模块、跨进程、跨应用程序,而回调一般用于模块内或高层模块调用底层模块。因为消息管理者往往是全局静态的,所以容易被不同模块调用,进而消息适合多模块之间的通信。这是在一个应用程序里。如果一个应用程序想和另一个应用程序通信,也可以用消息,因为每个应用程序都是操作系统的子模块,操作系统有相当于全局静态的消息管理者。而采用回调的方式,直接持有另一个模块、或进程、或应用程序的接口是危险的,不被允许的。

【何时用消息、何时用回调】

  • 首先考虑用消息,尤其涉及跨模块首先考虑消息(这个前提是要先知道什么时候是跨模块了)
  • 若按照消息的设计发现,一个消息只有一个发送者和一个接收者,考虑用回调
  • 若按照消息的设计发现,发送后要立马接收保持同步,必须用回调

【事件、回调、消息的区别】

事件也是一种广义回调,事件通常只有一个事件触发者,多个事件监听者;回调调用者和被调用者都只有一个;消息发送者和接收者都可以有多个。

事件的触发通常是由系统外部的动作或者行为导致的,而消息由系统内部某件事或某个条件发生了而产生。注册事件或接受消息者只需要知道某件事发生了或某个条件满足即可。

这里的系统指:对于用户而言,应用或App是一个系统,用户的输入操作是事件。对于系统内某个模块而言,其他模块的输入是事件。

由于事件的触发者通常可以只有一个,A触发事件a,B响应事件a后触发事件b,C响应事件b后触发事件c,依次类推,可以构成一个事件队列。而消息通常不能构成这样的消息队列。

【回调函数与钩子函数的区别】

例如某事发生了,进行了函数调用,但你想在这个事发生后进行一些处理,根据处理后的结果来确定是否进行函数调用。如果代码时自己写的,那么直接在函数调用前插入新的代码即可。如果代码是系统或者某个API之类的,你就不能乱修改代码,得向中间插入一个函数,这个函数就是钩子函数,可以说钩子函数也是回调函数的一种。之所以可以插入一个函数,是因为写系统和API的人考虑到有人想做自定义的行为。

那么某事发生了,后面进行的哪种函数调用,可以让一个钩子函数插入呢?如果发生的事和函数调用耦合的紧,就很难插入。显然,消息机制耦合度低,容易插入。所以大多数用钩子函数的时候都是对某个消息进行拦截。

【深入原理】

从计算机的角度,函数调用时怎么实现的,或者说过程是怎么样的,可以看以下链接深入理解下。

C++函数调用 入栈以及出栈_George熊的博客-CSDN博客_c++出栈函数

C++ 函数调用过程中栈区的变化——(栈帧、esp、ebp)_JMW1407的博客-CSDN博客_c++栈帧

C函数调用过程原理及函数栈帧分析 - stardsd - 博客园 

你可能感兴趣的:(C#,回调函数,消息机制,事件,函数调用过程,钩子函数)