协程的概念
协程就是 Kotlin 提供的一套线程封装的 API,使用协程可以让多线程之间的通信更加简单。总之一句话,协程可以简化异步编程。
举个例子:在IO线程发起网络请求,在主线程更新UI。
如果使用 Retrofit +回调的方式,代码类似下面这样
private fun normalRequest() {
apiService.getWxarticle().enqueue(object : retrofit2.Callback<WxArticleResponse> {
override fun onFailure(call: Call<WxArticleResponse>, t: Throwable) {
//请求失败
}
override fun onResponse(call: Call<WxArticleResponse>, response: retrofit2.Response<WxArticleResponse>) {
//请求成功,更新UI
}
})
}
如果使用 Retrofit +协程的方式,代码类似下面这样
private fun coroutineRequest2_6() {
launch(Dispatchers.Main) {//在主线程启动协程
val response = apiService.getWxarticle2()//在后台线程发起网络请求
tvResult.text = sb.toString()//在主线程更新UI
}
}
对比使用Retrofit+回调的方式,使用协程代码明显更加简洁清晰。
关于使用协程请求网络请参考 Kotlin协程请求网络。
协程的挂起
看一个例子
private fun invokeMethod() {
//注释1处,在主线程启动协程
scope.launch(Dispatchers.Main) {
Log.i(TAG, "getString: current thread " + Thread.currentThread().name)
val result = getString()//注释2处,调用挂起函数
etText.setText(result)//注释3处
}
}
//挂起函数
private suspend fun getString(): String {
//切换到IO线程
return withContext(Dispatchers.IO) {
Log.i(TAG, "getString: current thread " + Thread.currentThread().name)
"empty string"
}
}
输出结果
getString: current thread main
getString: current thread DefaultDispatcher-worker-2
注释1处,在主线程启动一个协程,就是指花括号里的这段代码,scope.launch(Dispatchers.Main) {...}
。
注释2处,当协程运行到挂起函数的时候,这个协程会被挂起。什么意思呢?
-
从挂起函数
getString()
的第一个挂起点开始当前线程(在这个例子中是主线程)不再运行协程中的代码了。第一个挂起点:可以暂时理解为withContext(Dispatchers.IO){...}
花括号里面的代码块。 -
挂起函数执行完毕以后,协程从挂起点恢复,重新切回到当前线程(在这个例子中是主线程)继续执行协程中的代码。
etText.setText(result)
协程的挂起是非阻塞的是什么意思呢?
这个其实非常非常简单,没有什么高深的东西。看代码。
- 阻塞的例子。
//在主线程执行
btnSuspend.setOnClickListener {
tvText1.text = getStringSuspend()
tvText2.text = "兴百姓苦,亡百姓苦"
}
/**
* 模拟耗时操作,2秒后返回字符串
*/
private fun getStringSuspend(): String {
//让主线程睡两秒钟,其实就是阻塞了主线程
Thread.sleep(2000)
return "峰峦如聚,波涛如怒,山河表里潼关路,望西都意踌躇,伤心秦汉经行处,宫阙万间都做了土。"
}
在上面的例子中,tvText1获取显示字符串是一个耗时操作。注释1处,tvText2显示text肯定是在tvText1显示之后。也就是说当前线程:主线程 被阻塞了。当前线程被阻塞以后当前线程的后面的代码是无法执行的,必须等待阻塞结束。
- 协程的非阻塞
//在主线程执行
private var scope: CoroutineScope = CoroutineScope(Dispatchers.Main)
btnUnSuspend.setOnClickListener {
scope.launch {
tvText1.text = getStringUnSuspend()
}
//注释1处注意:这个代码一定要卸载协程的代码之外
tvText2.text = "兴百姓苦,亡百姓苦"
}
/**
* 模拟耗时操作,2秒后返回字符串。
*/
private suspend fun getStringUnSuspend(): String {
delay(2000)
return "峰峦如聚,波涛如怒,山河表里潼关路,望西都意踌躇,伤心秦汉经行处,宫阙万间都做了土。"
}
现在,tvText2先显示text,然后2秒后tvText1才显示了文字。这里主线程没有没阻塞,协程的delay(2000)
,实际上是在后台线程阻塞(延迟了)2秒,阻塞的是后台线程。
注释1处注意:这个代码一定要协程的代码之外。如果也写在launch的大括号之内了,那么tvText2也是在tvText1显示文字之后再显示。这也说明了在协程内部,代码也是顺序执行的,一个父协程会等待子协程执行完毕之后再继续往下执行。
scope.launch {
tvText1.text = getStringUnSuspend()
tvText2.text = "兴百姓苦,亡百姓苦"
}
/**
* 模拟耗时操作,2秒后返回字符串。正确的的非阻塞,不阻塞当前线程,阻塞后台线程。
*/
private suspend fun getStringUnSuspend(): String {
delay(2000)
return "峰峦如聚,波涛如怒,山河表里潼关路,望西都意踌躇,伤心秦汉经行处,宫阙万间都做了土。"
}
结论:非阻塞式挂起,就是说挂起函数切到指定的线程执行去了,执行完毕以后会再切回到当前线程继续执行协程中的代码。在挂起函数切到别的线程执行的这段期间,当前线程是可以继续运行其他代码的。
关于协程的挂起记住一句话就行:挂起函数挂起协程时,不会阻塞协程所在的线程。
参考链接:
网友评论