前言
首先,
run
和runCatching
是同步的,而runBlocking
和runInterruptible
是异步的。
run
和runCatching
是 Kotlin 标准库的一部分,可以在所有支持的平台上使用。runBlocking
和runInterruptible
是 Coroutines 的一部分。
一、run
run
是一个作用域函数。可以在一个对象上调用它,代码块会直接访问对象的属性和方法,而不需要this
(但你也可以使用this
)。另外,run
可以返回一个结果,这个结果可以在后续的步骤中使用。
Kotlin
val event = Event(
id = UUID.randomUUID(),
value = 10,
message = null)
val isEven = event
.run {
value % 2 == 0
}
println("Is Event.value even? $isEven.")
打印结果:Is Event.value even? true.
也可以在run的作用域代码块中修改message的值。
run
和 apply
有什么区别呢?好吧,主要的区别在于他们的返回值
run
是灵活的。它可以返回任何类型,不仅仅是它被调用的对象的类型。另一方面, apply
总是返回对象本身,这对于链式对象配置非常好。
此外,如前所述, run
可以独立于对象运行。 apply
总是需要一个对象来工作。
二、runCatching
它是
run
的一个变体。runCatching
实际上是一个try...catch
块。但有一个重要的区别。它将块执行的结果封装到一个
Result
对象中。另一个优点是runCatching
块执行的结果可以被比较。
Kotlin
val event = Event(
id = UUID.randomUUID(),
value = 10,
message = null,
badModifyablePropertyForDemoPurposes = "Some string")
val result = event.runCatching {
value / 0 //错误代码
}.onFailure {
println("We failed to divide by zero. Throwable: $it")
}.onSuccess {
println("Devision result is $it")
}
println("Returned value is $result")
打印结果:
Kotlin
I We failed to divide by zero. Throwable:
java.lang.ArithmeticException: divide by zero
I Returned value is: Failure(java.lang.ArithmeticException: divide by zero)
所以,正如你所看到的,使用 runCatching
提供了几个优势。块执行的失败或成功的结果可以用链式的处理,还可以拿到这个错误的变量在流中抛出。
异步的 runBlocking 和 runInterruptable与run和runcatching的唯一共同点是都能够执行一段代码块。然而,它们显著的区别在于功能和用例方面。
三、 runBlocking
主要用例:
-
- 当需要在一些测试中阻塞直到协程完成执行的时候。
-
- 为了执行一些顶级的代码(也就是说,不能在协程中运行的代码)。
主要的问题是,为什么只有这些呢?
为什么我在 StackOverflow 上看到的回答中,比如说,需要避免使用这个函数?是的,它阻塞了当前的线程,但我们可以产生自己的线程,这样就不会影响其他的代码。
现在,让我们看一下使用 viewModel 的更现实的场景。
Kotlin
class MainViewModel : ViewModel() {
fun runFlows() {
thread(
name = "Manual Thread",
) {
println("Thread: ${Thread.currentThread()}")
runCollection()
}
}
private suspend fun collect(action: (Int) -> Unit) {
runBlocking {
val eventGenerator = EventGenerator()
eventGenerator
.coldFlow
.collect {
action(it)
}
}
}
private fun runCollection() {
viewModelScope.launch {
collect {
println("Collection in runCollections #1: $it: ${Thread.currentThread()}")
}
}
viewModelScope.launch {
collect {
println("Collection in runCollections #2: $it: ${Thread.currentThread()}")
}
}
}
}
打印结果:
Kotlin
00:40:44.332 I Emitting 0
00:40:44.334 I Collection in runCollections #1: 0: Thread[main,5,main]
00:40:45.336 I Emitting 1
00:40:45.336 I Collection in runCollections #1: 1: Thread[main,5,main]
00:40:46.337 I Emitting 2
00:40:46.338 I Collection in runCollections #1: 2: Thread[main,5,main]
请注意,生成线程不会提供任何内容,它只是生成一个根本不影响异步操作的线程。 viewModelScope 绑定到主调度程序,最终进入主线程(当然,这是一个简化的解释,因为深入研究了调度程序的细节以及 Main 之间的区别、Main.immediate 不在本文中)。
如果 runBlocking 从 collect() 实现中删除,则调用 runFlows() 打印
Kotlin
01:05:48.180 I Emitting 0
01:05:48.181 I Collection in runCollections #1: 0: Thread[main,5,main]
01:05:48.181 I Emitting 0
01:05:48.181 I Collection in runCollections #2: 0: Thread[main,5,main]
01:05:49.182 I Emitting 1
01:05:49.182 I Collection in runCollections #1: 1: Thread[main,5,main]
01:05:49.183 I Emitting 1
01:05:49.183 I Collection in runCollections #2: 1: Thread[main,5,main]
这就是我们通常期望的异步操作。是的,这是预料之中的,但如果您不牢记 viewModelScope 的绑定内容,则并不明显。
将 thread 移动到 collect() 函数
Kotlin
private suspend fun collect(action: (Int) -> Unit) {
thread(
name = "Manual Thread",
) {
runBlocking {
val eventGenerator = EventGenerator()
eventGenerator
.coldFlow
.collect {
action(it)
}
}
}
}
也给出了类似的结果:
Kotlin
01:08:51.944 I Emitting 0
01:08:51.944 I Emitting 0
01:08:51.946 I Collection in runCollections #2: 0: Thread[Manual Thread,5,main]
01:08:51.947 I Collection in runCollections #1: 0: Thread[Manual Thread,5,main]
01:08:52.948 I Emitting 1
01:08:52.948 I Emitting 1
01:08:52.948 I Collection in runCollections #1: 1: Thread[Manual Thread,5,main]
01:08:52.948 I Collection in runCollections #2: 1: Thread[Manual Thread,5,main]
使用 runBlocking 很容易失去对异步操作的跟踪,并失去用于自动管理挂起和切换要执行的协程的强大协程功能。如果您不是 Java 和 Android 线程方面的专家,并且由于某种原因协程实现不符合您的需求,那么这不是最好的异步方案。感觉在移动应用程序开发中它应该主要用于测试。
四、runInterruptible
文档指出将可中断的方式调用代码块。此函数不会生成线程并遵循您作为参数提供的调度程序。
在 viewModel 中添加了新方法。
Kotlin
fun runInterruptible() {
viewModelScope.launch {
println("Start")
kotlin.runCatching {
withTimeout(100) {
runInterruptible(Dispatchers.IO) {
interruptibleBlockingCall()
}
}
}.onFailure {
println("Caught exception: $it")
}
println("End")
}
}
private fun interruptibleBlockingCall() {
Thread.sleep(3000)
}
打印日志:
Kotlin
I Start
I Caught exception: kotlinx.coroutines.TimeoutCancellationException:
Timed out waiting for 100 ms
I End
注意调用链。 runCatching (try...catch )然后是 withTimeout 。
withTimeout 抛出异常,但没有看到它的日志。如果我添加 try...catch 或 runCatching 那么可以检测到异常,没有它协程就默默地停止工作了。
没有找到这种行为的原因,并且在跟踪器中没有看到任何报告。因此请记住使用
try...catch 或
withTimeoutOrNull 。