Kotlin协程小记
Kotlin协程是什么?
官方说协程可以被认为是轻量级线程,但是在我的使用体验下来,Kotlin协程更像是一个助手,协助我们更好地使用线程,它可以在不同的线程间灵活切换,让代码以我们想要的顺序去执行,最直观的感受就是我可以少写很多回调操作,优雅地处理异步任务。
引入协程
在build.gradle
文件中添加:
1 | dependencies { |
使用
runBlocking
会阻塞协程所在线程,可以直接返回运行结果,举个栗子:
1 | fun testRunBlocking() { |
1 | 09:16:16.016 测试开始 运行在线程1 |
withContext
只能在协程块(这个后面再说)内使用,所以在外面用runBlocking
包了一层。它会阻塞协程所在线程,可以直接返回运行结果,举个栗子:
1 | fun testWithContext() { |
1 | 09:34:39.039 测试开始 运行在线程1 |
launch
不会阻塞协程所在线程,不可以直接返回运行结果,但是会返回一个Job
对象(这个后面再说),举个栗子:
1 | fun testLaunch() { |
1 | 09:45:39.039 测试开始 运行在线程1 |
async
不会阻塞协程所在线程,可以返回运行结果,但是需要用await
获取,await
只能在协程块(这个后面再说)内使用,所以在外面用runBlocking
包了一层,举个栗子:
1 | fun testAsync() { |
1 | 09:50:49.049 测试开始 运行在线程1 |
可以看到我们通过await
能拿到运行结果,而且await
会阻塞所在线程。
之前多次提及的协程块,就是runBlocking
、launch
等方法里面的代码块,一般都是执行耗时操作,我们还可以把这些操作抽取出来作为一个方法,这样的方法需要用suspend
关键字修饰。suspend
方法里面的代码块也是协程块,而且suspend
方法也只能在协程块内使用(套娃?),suspend
的意义更多是标记这是一个耗时方法,协程专用,避免误用导致主线程阻塞。上面的代码还可以这样写:
1 | fun testAsync() { |
1 | 09:53:29.029 测试开始 运行在线程1 |
嗯,结果是一样的。
庐山真面目
了解了suspend
后,我们再看看协程这些个方法的庐山真面目:
1 | public fun <T> runBlocking( |
1 | public suspend fun <T> withContext( |
1 | public fun CoroutineScope.launch( |
1 | public fun <T> CoroutineScope.async( |
1 | public abstract suspend fun await(): T |
1 | public suspend fun delay(timeMillis: Long): Unit |
可以看到withContext
、await
和delay
都是suspend
方法,也难怪必须要在协程块内才可以使用了。我们也能清楚地看到各方法的返回值,runBlocking
和withContext
是直接返回运行结果的,async
返回一个Deferred
对象,我们需要调用其await
方法获取运行结果,launch
则返回一个Job
对象,这个有什么用呢?还有传入的CoroutineContext
和CoroutineStart
又是什么玩意?且看下文。
直译就是协程上下文,我们可以传入线程调度器指定协程的线程调度,有四种类型:
Dispatchers.Default
协程块在子线程运行。
Dispatchers.Main
协程块在主线程运行,Android即是在UI线程运行。
Dispatchers.Unconfined
协程块在协程所在线程运行,但是如果遇到挂起操作,则会在子线程运行后面的代码。
Dispatchers.IO
协程块在子线程运行。
协程的启动模式,有四种类型:
CoroutineStart.DEFAULT
协程块立即运行。
CoroutineStart.LAZY
协程块在调用启动方法后才运行。
CoroutineStart.ATOMIC
协程块立即运行,且不可取消。
CoroutineStart.UNDISPATCHED
协程块立即运行,会忽略设置的线程调度器而在协程所在线程运行,但是如果遇到挂起操作,则会在线程调度器指定线程运行后面的代码。
提供操作协程块运行的能力,Deferred
是其子类,他们都有以下几个方法:
start
启动协程块所在协程,一般用于
CoroutineStart.LAZY
启动模式。cancel
取消协程块运行,但不会立马生效,有一定延迟。
join
这是一个
suspend
方法,会阻塞协程所在线程,直到协程块运行完毕。cancelAndJoin
先调用
cancel
,再调用join
,会阻塞协程所在线程,直到协程块运行完毕。
我们上面用launch
和async
启动协程时,都是在GlobalScope
这个全局的生命周期内,它就类似于Application
会存活在整个应用运行期间。这会带来什么影响呢?那就是我们可能没有及时回收相关资源而造成内存泄漏。因此官方提供了LifecycleScope
和ViewModelScope
这种非全局的生命周期。例如在AppCompatActivity
和Fragment
中,协程就会受到LifecycleScope
生命周期的约束,当走到onDestroy
这一步时,还在运行且可取消的协程会被取消并做好资源回收。但是我们非要使用GlobalScope
呢?也不是不行,自行在合适的地方调用cancel
取消协程,甚至可以直接调用GlobalScope.cancel
取消其范围内所有可取消协程。LifecycleScope
和ViewModelScope
同样可以直接调用cancel
取消其范围内的所有可取消协程。
除了手动调用cancel
取消协程,我们还可以用withTimeout
设置超时自动取消,举个栗子:
1 | fun testWithTimeout() { |
1 | 17:07:26.026 测试开始 运行在线程1 |
对比withTimeout
,还有一个withTimeoutOrNull
方法,它们都会阻塞所在线程,若不超时可以正常返回结果。其不同之处在于withTimeout
超时后会抛出超时错误,而withTimeoutOrNull
直接返回null
却不抛错误。
小结
使用协程后,对于耗时操作,我们不但可以优雅地切换到子线程去处理,还可以拿到运行结果优雅地回到之前线程使用,免去了回调的俗套,轻松拿捏多线程的执行顺序,转异步为同步,化腐朽为神奇。
Kotlin协程小记