最近在学习异步机制的过程中,发现实现异步的一个重要方式是通过回调函数(callback),而关于回调函数知乎上有一个非常热门的问题“回调函数(callback)是什么?”,里面汇集了非常多的回答,有好多既精辟又准确的回答得到上千个赞同,推荐对与回调函数不太理解的同学把每个回答都看一看,经过一番对比就会有自己的理解。下面就是我对回调函数的理解:
先来看一下得票最高的回答中构建的一个示例
even.py
#回调函数1
#生成一个2k形式的偶数
def double(x):
return x * 2
#回调函数2
#生成一个4k形式的偶数
def quadruple(x):
return x * 4
from even import *
#中间函数
#接受一个生成偶数的函数作为参数
#返回一个奇数
def getOddNumber(k, getEvenNumber):
return 1 + getEvenNumber(k)
#起始函数,这里是程序的主函数
def main():
k = 1
#当需要生成一个2k+1形式的奇数时
i = getOddNumber(k, double)
print(i)
#当需要一个4k+1形式的奇数时
i = getOddNumber(k, quadruple)
print(i)
#当需要一个8k+1形式的奇数时
i = getOddNumber(k, lambda x: x * 8)
print(i)
if __name__ == "__main__":
main()
主函数中把函数“double”、“quadruple”、“lambda x: x * 8”作为参数传入函数getOddNumber,这其中被传入的函数就是回调函数,而getOddNumber叫做中间函数,把回调函数作为参数传入中间函数的操作叫做登记回调函数,中间函数执行到回调函数的位置叫做触发回调函数,执行回调函数叫做调用回调函数,回调函数内的执行叫做响应回调事件。根据回调函数与主函数返回结果的顺序不同分类,可将中间函数调用分为阻塞式调用(同步调用)和延迟式调用(非阻塞调用)(异步调用),阻塞式调用的返回结果一定在主函数返回结果前,而延迟式调用的返回结果可能在主函数返回结果后。因此这里中间函数的作用有两点:
- 将回调函数与主函数解耦;
- 实现延迟式调用,例如threading.Thread(target=getOddNumber, args=(k, quadruple, ))
原回答中,答主的“阻塞式回调”和“延迟式回调”的说法(如下图所示),我认为不太合适,会造成读者误解为阻塞与延迟是由回调函数引起的,而事实上是因为中间函数的调用产生阻塞或延迟
下面图中是该回答中的一个评论,其中存在两个值得商榷的地方:
- 阻塞和非阻塞关注的是主函数在等待调用结果时的状态,同步和异步关注的是主函数与被调用函数之间的消息通信方式,不是一个维度的概念,阻塞式调用完全可以调用异步函数,实现异步阻塞(虽然大部分情况是多此一举)
- 回调是实现异步的重要方式之一,“异步回调”是什么意思就很让人费解了。
因而我这里将中间函数调用分为“阻塞式调用(同步调用)”和“延迟式调用(非阻塞调用)(异步调用)”。
结合企业微信API文档中的回调服务页面,加深对回调的理解
下图是企业微信中回调服务的逻辑图
以Flask框架开发回调服务为例:
企业微信的主函数相当于主函数;
企业微信的通知推送服务相当于中间函数;
开发者开发的视图,回调函数;
开发者将回调URL写入企业微信,相当于登记回调函数;
企业微信通知推送服务触发通知推送,相当于触发回调函数;
企业微信通知推送服务将通知推送至开发者服务器,相当于调用回调函数;
通知推送到开发者的服务器,执行视图,相当于响应回调事件
由于对企业微信的通知推送服务(中间函数)的调用并不会造成企业微信其他服务(主函数)的阻塞,因而为非阻塞调用
如果企业微信需要真实的响应结果作为输入传入下一个函数中,或者回调服务一定会在5秒内响应,则开发者可以开发同步视图;
如果企业微信不需要真实的响应结果作为输入传入下一个函数中,或者回调服务一定不会在5秒内响应,则开发者必须开发异步视图,以便及时响应;
参考:
- 回调函数(callback)是什么? - no.body的回答 - 知乎
https://www.zhihu.com/question/19801131/answer/27459821 - 怎样理解阻塞非阻塞与同步异步的区别? - Yi Lu的回答 - 知乎
https://www.zhihu.com/question/19732473/answer/20851256 - 企业微信API文档:https://open.work.weixin.qq.com/api/doc/90001/90143/91116