Tho*_*ook 7 kotlin kotlin-coroutines
我很习惯使用 RX 来处理并发,但是,在我目前的工作中,我们混合使用了 AsyncTask、Executors + Handlers、Threads 和一些 LiveData。现在我们正在考虑转向使用 Kotlin Coroutines(事实上已经开始在代码库的某些地方使用它)。
因此,我需要开始关注协程,最好是利用我现有的并发工具知识来加快进程。
我曾尝试为他们关注 Google 代码实验室,虽然它让我有所了解,但它也提出了许多悬而未决的问题,因此我尝试通过编写一些代码、调试和查看日志输出来弄脏我的手。
据我了解,协程由 2 个主要构建块组成;挂起函数是你工作的地方,协程上下文是你执行挂起函数的地方,这样你就可以处理协同程序将在哪些调度程序上运行。
下面我有一些代码,它的行为符合我的预期。我已经使用 Dispatchers.Main 设置了一个协程上下文。因此,正如预期的那样,当我启动协程时,由于以下原因,getResources它最终会阻塞 UI 线程 5 秒Thread.sleep(5000):
private const val TAG = "Coroutines"
class MainActivity : AppCompatActivity(), CoroutineScope {
override val coroutineContext: CoroutineContext = Job() + Dispatchers.Main
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
log("onCreate", "launching coroutine")
launch {
val resource = getResource()
log("onCreate", "resource fetched: $resource")
findViewById<TextView>(R.id.textView).text = resource.toString()
}
log("onCreate", "coroutine launched")
}
private suspend fun getResource() : Int {
log("getResource", "about to sleep for 5000ms")
Thread.sleep(5000)
log("getResource", "finished fetching resource")
return 1
}
private fun log(methodName: String, toLog: String) {
Log.d(TAG,"$methodName: $toLog: ${Thread.currentThread().name}")
}
}
Run Code Online (Sandbox Code Playgroud)
当我运行此代码时,我看到以下日志:
2020-05-28 11:42:44.364 9819-9819/? D/Coroutines: onCreate: launching coroutine: main
2020-05-28 11:42:44.376 9819-9819/? D/Coroutines: onCreate: coroutine launched: main
2020-05-28 11:42:44.469 9819-9819/? D/Coroutines: getResource: about to sleep for 5000ms: main
2020-05-28 11:42:49.471 9819-9819/com.example.coroutines D/Coroutines: getResource: finished fetching resource: main
2020-05-28 11:42:49.472 9819-9819/com.example.coroutines D/Coroutines: onCreate: resource fetched: 1: main
Run Code Online (Sandbox Code Playgroud)
可以看到,所有的日志都来自主线程,在Thread.sleep(5000). 在这 5 秒的间隙中,UI 线程被阻塞,我可以通过查看模拟器来确认这一点;它不会呈现任何 UI,因为onCreate它被阻止了。
现在,如果我更新getResources函数以使用 suspend fundelay(5000)而不是Thread.sleep(5000)像这样使用:
private suspend fun getResource() : Int {
log("getResource", "about to sleep for 5000ms")
delay(5000)
log("getResource", "finished fetching resource")
return 1
}
Run Code Online (Sandbox Code Playgroud)
然后我最终看到的东西让我感到困惑。我理解delay与 不一样Thread.sleep,但因为我在由 支持的协程上下文中运行它Dispatchers.Main,我希望看到与使用相同的结果Thread.sleep。
相反,我看到的是 UI 线程在 5 秒延迟发生时没有被阻塞,日志如下所示:
2020-05-28 11:54:19.099 10038-10038/com.example.coroutines D/Coroutines: onCreate: launching coroutine: main
2020-05-28 11:54:19.111 10038-10038/com.example.coroutines D/Coroutines: onCreate: coroutine launched: main
2020-05-28 11:54:19.152 10038-10038/com.example.coroutines D/Coroutines: getResource: about to sleep for 5000ms: main
2020-05-28 11:54:24.167 10038-10038/com.example.coroutines D/Coroutines: getResource: finished fetching resource: main
2020-05-28 11:54:24.168 10038-10038/com.example.coroutines D/Coroutines: onCreate: resource fetched: 1: main
Run Code Online (Sandbox Code Playgroud)
我可以看到在这种情况下 UI 线程没有被阻塞,因为 UI 在延迟发生时呈现,然后文本视图在 5 秒后更新。
所以,我的问题是,在这种情况下,延迟如何不阻塞 UI 线程(即使我的挂起函数中的日志仍然表明该函数正在主线程上运行......)
Ten*_*r04 11
将挂起函数视为使用接受回调的函数的一种方式,但不需要您将该回调传递给它。相反,回调代码是挂起函数调用下的一切。
这段代码:
lifecycleScope.launch {
myTextView.text = "Starting"
delay(1000L)
myTextView.text = "Processing"
delay(2000L)
myTextView.text = "Done"
}
Run Code Online (Sandbox Code Playgroud)
有点像:
myTextView.text = "Starting"
handler.postDelayed(1000L) {
myTextView.text = "Processing"
handler.postDelayed(2000L) {
myTextView.text = "Done"
}
}
Run Code Online (Sandbox Code Playgroud)
永远不应期望挂起函数会阻塞。如果他们这样做了,他们的组成是不正确的。挂起函数中的任何阻塞代码都应该包含在它的背景中,例如withContextor suspendCancellableCoroutine(这是较低级别的,因为它直接与协程延续一起工作)。
如果您尝试编写这样的挂起函数:
suspend fun myDelay(length: Long) {
Thread.sleep(length)
}
Run Code Online (Sandbox Code Playgroud)
您将收到“不适当的阻塞方法调用”的编译器警告。如果将其推送到后台调度程序,则不会收到警告:
suspend fun myDelay(length: Long) = withContext(Dispatchers.IO) {
Thread.sleep(length)
}
Run Code Online (Sandbox Code Playgroud)
如果您尝试将其发送到Dispatchers.Main,您将再次收到警告,因为编译器认为主线程上的任何阻塞代码都是不正确的。
这应该让您了解挂起函数应该如何操作,但请记住,编译器无法始终将方法调用识别为阻塞。
| 归档时间: |
|
| 查看次数: |
7071 次 |
| 最近记录: |