1、背景
在硬件发展过程中,最初使用单核单CPU到后期的单核多CPU,再到多核多CPU,貌似已经到了极限,但是单核CPU的性能还在不停的提升,如果说多进程对应多CPU、多线程对应多核CPU,协程的目的就是在充分利用不断提高性能的单核CPU潜能。在业务开发过程中有很多性能瓶颈的耗时操作,比如网络IO、文件IO等。在使用Java做Android开发的时候,为了将耗时操作放在子线程去完成,大部分解决手段是通过异步回调的方式来实现,回调代码写起来比较繁琐以及可读性也不是很友好。
协程使用协程挂起来提供一种避免阻塞线程,并用更简单可控的操作替代线程阻塞的方法,使异步回调的方法看上去是同步的方式,这样,我看可以按照串行的思维模式去组织原本分散在不同上下文环境中的代码逻辑,避免了处理复杂状态同步的问题,增加写代码的直观性和阅读友好性。
2、协程的概念
协程是创造出来解决异步问题,线程的调度是操作系统层面完成是抢占式的;协程是非抢占式的,是协作运行的,是由应用层完成调度,协程在挂起的时候不会堵塞线程,只会将当前运行的状态存在内存中,当协程执行完成后,会恢复之前保存的状态继续运行。
3、使用
下面将使用协程实现一个主线程发启按钮点击,启动一个协程去做一个耗时任务,完成耗时任务后,将结果返回到UI线程的异步耗时操作流程。
启动协程在接收到点击事件的时候我们只是调用了ContextAsyncCoroutineManager的startCoroutine方法,然后将耗时操作函数传以及后续处理的代码block传入;ContextAsyncCoroutineManager封装了启动协程的代码,代码如下:
ContextAsyncCoroutineManager可以看到ContextAsyncCoroutineManager协程入口启动函数startCoroutine需要传入一个被suspend关键之修饰的block代码块,协程启动处需要使用suspend关键字修饰,在按钮被点击的时候,我们传入的函数是calculateAndJointText,做一个字符串拼接,然后每次休眠200毫的耗时任务;这里有一个AsyncTask类器代码:
AsyncTaskAsyncTask是处理耗时操作的线程池,在处理耗时操作的时候启动一个线程,将耗时任务放在该线程池中去处理。在平时开发过程中都会有自己的线程池管理应用的线程,这里只是一个很简易的线程池做一个演示。
调用block.startCoroutine(ContextContinuation(AsyncContext()))函数启动协程的时候需要传入了一个ContextContinuation,ContextContinuation的实例化又传入一个AsyncContext,其中ContextContinuation的具体实现如下:
ContextContinuationContinuation:协程的编译记住状态特征主要实现者就是这个类,是描述程序控制状态的抽象,通过数据结果来表示执行到指定位置的计算过程,其结构可以由程序程序语言访问;即当一个协程执行到什么地方,需要用到什么field或者保存什么状态,这些过程都在当前运行时的这个实例中管理;
ContextContinuation是继承自Continuation,当协程执行完成,在耗时代码块内调用continuation.resume(textResult)的时候会回调到ContextContinuation的resumeWith里;
AsyncContextAsyncContext:这里的作用是使用Context将工作线程切换到UI线程,替换Continuation到主专门做住线程切换的一个UIContinuationWrapper类,每个ContextContinuation可以有多个CoroutineContext;
协程Context,每个协程运行的时候都会有一个或者多个CoroutineContext对象,每个Context都有属于自己的一个Key来标记自己。我们的AsyncContext的Key是ContinuationInterceptor类型,并且实现了ContinuationInterceptor接口,上面的interceptContinuation函数可以拦截外部启动协程传入的Continuation对象,我们的需求是使用该特性做一次Continuation的篡改行为,替换为定义专门做UI线程切换的UIContinuationWrapper,在自己篡改好后,不能直接拦截掉本次Continuations实例,需要继续传递给其它的拦截者。在结果回来调用就会调用到UIContinuationWrapper的resumeWith方法,从而完成将执行结果返回到UI线程,实现自动切换线程。
UiContinuationWrapper使用Kotlin协程基础API实现一次异步任务的代码就已经完成,本次只是借助这个小例子来介绍相关的一些概念。其实还有很多具体使用和实现没有讲,比如:在开发过程中其实我们肯定会涉及到多线程传值的问题,也可以通过我们的Context去完成;以及我们需要使用协程基础API去封装一个比较通用完善的Kotlin异步任务框架等。
网友评论