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

前言

首先,runrunCatching同步的,而 runBlockingrunInterruptible异步的。

runrunCatching 是 Kotlin 标准库的一部分,可以在所有支持的平台上使用。 runBlockingrunInterruptible 是 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的值。

runapply 有什么区别呢?好吧,主要的区别在于他们的返回值

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

主要用例:

    1. 当需要在一些测试中阻塞直到协程完成执行的时候。
    1. 为了执行一些顶级的代码(也就是说,不能在协程中运行的代码)。

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

为什么我在 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

相关推荐
m0_5557629025 分钟前
Matlab 频谱分析 (Spectral Analysis)
开发语言·matlab
哲科软件1 小时前
跨平台开发的抉择:Flutter vs 原生安卓(Kotlin)的优劣对比与选型建议
android·flutter·kotlin
浪裡遊1 小时前
React Hooks全面解析:从基础到高级的实用指南
开发语言·前端·javascript·react.js·node.js·ecmascript·php
lzb_kkk2 小时前
【C++】C++四种类型转换操作符详解
开发语言·c++·windows·1024程序员节
好开心啊没烦恼2 小时前
Python 数据分析:numpy,说人话,说说数组维度。听故事学知识点怎么这么容易?
开发语言·人工智能·python·数据挖掘·数据分析·numpy
简佐义的博客3 小时前
破解非模式物种GO/KEGG注释难题
开发语言·数据库·后端·oracle·golang
程序员爱钓鱼3 小时前
【无标题】Go语言中的反射机制 — 元编程技巧与注意事项
开发语言·qt
Frank学习路上3 小时前
【IOS】XCode创建firstapp并运行(成为IOS开发者)
开发语言·学习·ios·cocoa·xcode
2301_805054564 小时前
Python训练营打卡Day59(2025.7.3)
开发语言·python