Kotlin 同步与异步执行:run、runCatching、runBlocking 与 runInterruptible

发布时间:2024年01月16日

前言?

????????首先,run?和?runCatching?是同步的,而?runBlocking?和?runInterruptible?是异步的。?

? ??run?和?runCatching?是 Kotlin 标准库的一部分,可以在所有支持的平台上使用。?runBlocking?和?runInterruptible?是 Coroutines 的一部分。


?一、run

????????run?是一个作用域函数。可以在一个对象上调用它,代码块会直接访问对象的属性和方法,而不需要?this?(但你也可以使用?this)。另外,?run?可以返回一个结果,这个结果可以在后续的步骤中使用。?

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?块执行的结果可以被比较。

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")

打印结果:

?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

主要用例:

  1. 1.?当需要在一些测试中阻塞直到协程完成执行的时候。

  2. 2.?为了执行一些顶级的代码(也就是说,不能在协程中运行的代码)。

主要的问题是,为什么只有这些呢?

为什么我在 StackOverflow 上看到的回答中,比如说,需要避免使用这个函数?是的,它阻塞了当前的线程,但我们可以产生自己的线程,这样就不会影响其他的代码。

现在,让我们看一下使用 viewModel 的更现实的场景。

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()}")
            }
        }
    }
}

打印结果:?

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() 打印

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() 函数

private suspend fun collect(action: (Int) -> Unit) {
        thread(
            name = "Manual Thread",
        ) {
            runBlocking {
                val eventGenerator = EventGenerator()
                eventGenerator
                    .coldFlow
                    .collect {
                        action(it)
                    }
            }
        }
    }

也给出了类似的结果:

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 中添加了新方法。

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)
}

打印日志:?

 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?。

文章来源:https://blog.csdn.net/csdn_aiyang/article/details/135621760
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。