传统的顺序编程语言要实现异步多是通过多线程的方式,创建线程需要调用操作系统的API,并且对线程的管理和线程间的通信以及竞争与死锁都是经典问题。现在很多语言都内置了语言级别的多线程和异步任务管理功能。我们看看异步编程能够解决什么问题,各个语言的实现有什么区别。

函数调用有同步调用,异步调用。同步调用就是面向过程的编程方式,函数以代码编写的先后顺序执行,这样的代码容易理解,也贴近计算机真正的执行逻辑。回调是实现异步调用的一个最简捷的途径,回调函数也是一个函数或过程,不过它是一个由调用方自己实现,供被调用方使用的特殊函数。异步调用的实现方式有多种,如果是多线程模式,异步的实现是由操作系统进行资源的调用,对程序编写要求高。如果是语言层面的实现,则是由语言本身进行资源调度,灵活性更高,编写程序更方便,也是目前高级语言的优势。实现异步调用的优势有很多,比如速度快,编程效率高。异步的缺点也很明显,程序执行逻辑被打乱,调试困难,容易出现由执行顺序与时间造成的的Bug。

C/C++

由于C/C++ 可以使用函数指针,可以将回调函数指针作为参数。但使用了回调函数程序并不是实现了异步执行,只是将处理逻辑前置,交由被调用方在合适的时间执行。所以关键点在异步执行的实现,回调函数只是在异步操作完成后获取通知结果。C语言的异步实现可以通过fork出新的进程,传入不同的参数实现。

Java

Java有语言级别的多线程技术。继承java.lang.Thread类并重写run方法,然后调用start方法就可以开启一个线程。这样创建的线程是没有返回值的,内部出现异常也会导致退出,只能使用额外的变量保存结果。Runnable也没有参数和返回值可以使用,不过Runnable是一个接口,使用上比Thread灵活。Callable位于java.util.concurrent包下,它也是一个接口,不过它支持泛型,能够返回自定义数据,并且有现成的线程池管理工具ExecutorService。

回调在Java中的实现就是定义一个接口,约定好在什么情况下会执行哪个方法,在使用回调函数的地方创建一个实现回调类的匿名类作为参数传递到另外一个函数处理。在Android界面编程中Activity中的onCreate方法就是回调函数,执行这个函数的不是自己,而是交给管理整个Activity生命周期的线程。

JavaScript

对JavaScript来说,Function就是一个特殊的Object,不需要像Java定义一个接口,回调函数可以直接作为函数参数。JavaScript是单线程的,异步机制是基于事件循环,浏览器有一个事件队列和专门用于处理事件的线程,会不断检查事件队列里有没有等待处理的事件。如果JavaScript线程很忙,没时间处理队列中的事件,就会造成浏览器卡死。

不过由于回调的滥用,回调依赖关系会造成非常复杂的代码结构。Promise的使用可以适当减少回调嵌套层次过多的问题。Promise的作用是把原本嵌套的函数串联起来,通过then函数可以串联很多原本需要嵌套使用回调的函数。Promise同样可以并行执行一些函数,并且等待所有函数执行完毕,任一函数的执行失败都会立即结束,并转入错误处理。async/await的目的是简化同步使用Promises的用法,将需要异步执行的函数用同步方式写出来,能更容易理解。

C#

C#使用委托来解决事件处理函数的定义和使用。通过声明delegate确定处理函数的参数、返回值,所有符合委托形式的方法都可以绑定到委托类上。事件可以对委托绑定事件处理函数,并且避免暴露委托类。委托相当于安全的函数指针,并且能聚合多个处理函数,而事件是对函数指针的保护和应用,比如禁止对委托的赋值,只能使用add和remove管理对事件的处理。

Go

原文链接:https://marskid.net/2017/12/06/async-howto/