直击灵魂一问:协程到底是怎么切换线程的?

深潜Kotlin协程(十二):Dispatchers 协程调度器 系列电子书:传送门Kotlin 协程库提供的一个重要的功能是让我们能决定协程应该运行在哪个线程(或线程池)上,这是通过使用调度器来完成的。在英语词典中,dispatcher 的意思是“负责将人员或者车辆运送到指需要地方的人,尤其是那些紧急车辆”。在 Kotlin 协程中, 可以决定某个协程在哪个线程上运行。Dispatchers in Kotlin Coroutines are a similar concept to RxJava Schedulers.如果不设置任何调度器,则默认选择 ,它被设计用于运 阅读详情

前置知识

CoroutineScope到底是什么?

CoroutineScope即协程运行的作用域,它的源码很简单

public interface CoroutineScope {
    public val coroutineContext: CoroutineContext
}

可以看出CoroutineScope的代码很简单,主要作用是提供CoroutineContext,协程运行的上下文 我们常见的实现有GlobalScope,LifecycleScope,ViewModelScope等

GlobalScope与ViewModelScope有什么区别?

public object GlobalScope : CoroutineScope {
    /**
     * 返回 [EmptyCoroutineContext].
     */
    override val coroutineContext: CoroutineContext
        get() = EmptyCoroutineContext
}

public val ViewModel.viewModelScope: CoroutineScope
    get() {
        val scope: CoroutineScope? = this.getTag(JOB_KEY)
        if (scope != null) {
            return scope
        }
        return setTagIfAbsent(
            JOB_KEY,
            CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
        )
    }

两者的代码都挺简单,从上面可以看出

  1. GlobalScope返回的为CoroutineContext的空实现
  2. ViewModelScope则往CoroutineContext中添加了Job与Dispatcher

我们先来看一段简单的代码

 fun testOne(){
  GlobalScope.launch {
            print("1:" + Thread.currentThread().name)
            delay(1000)
            print("2:" + Thread.currentThread().name)
        }
 }
 //打印结果为:DefaultDispatcher-worker-1
    fun testTwo(){
        viewModelScope.launch {
            print("1:" + Thread.currentThread().name)
            delay(1000)
            print("2:" + Thread.currentThread().name)
        }
    }
    //打印结果为: main

上面两种Scope启动协程后,打印当前线程名是不同的,一个是线程池中的一个线程,一个则是主线程 这是因为ViewModelScope在CoroutineContext中添加了
Dispatchers.Main.immediate的原因

我们可以得出结论:协程就是通过Dispatchers调度器来控制线程切换的

什么是调度器?

从使用上来讲,调度器就是我们使用的Dispatchers.Main,Dispatchers.Default,Dispatcher.IO等 从作用上来讲,调度器的作用是控制协程运行的线程 从结构上来讲,Dispatchers的父类是ContinuationInterceptor,然后再继承于CoroutineContext 它们的类结构关系如下:

这也是为什么Dispatchers能加入到CoroutineContext中的原因,并且支持+操作符来完成增加

什么是拦截器

从命名上很容易看出,ContinuationInterceptor即协程拦截器,先看一下接口

interface ContinuationInterceptor : CoroutineContext.Element {
    // ContinuationInterceptor 在 CoroutineContext 中的 Key
    companion object Key : CoroutineContext.Key<ContinuationInterceptor>
    /**
     * 拦截 continuation
     */
    fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T>

    //...
}

从上面可以提炼出两个信息

  1. 拦截器的Key是单例的,因此当你添加多个拦截器时,生效的只会有一个
  2. 我们都知道,Continuation在调用其Continuation#resumeWith()方法,会执行其suspend修饰的函数的代码块,如果我们提前拦截到,是不是可以做点其他事情?这就是调度器切换线程的

原理

上面我们已经介绍了是通过Dispatchers指定协程运行的线程,通过interceptContinuation在协程恢复前进行拦截,从而切换线程

带着这些前置知识,我们一起来看下协程启动的具体流程,明确下协程切换线程源码具体实现

协程线程切换源码分析

launch方法解析

我们首先看一下协程是怎样启动的,传入了什么参数

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

总共有3个参数:

  1. 传入的协程上下文
  2. CoroutinStart启动器,是个枚举类,定义了不同的启动方法,默认是CoroutineStart.DEFAULT
  3. block就是我们传入的协程体,真正要执行的代码

这段代码主要做了两件事:

  1. 组合新的CoroutineContext
  2. 再创建一个 Continuation

组合新的CoroutineContext

public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
    val combined = coroutineContext + context
    val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined
    return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
        debug + Dispatchers.Default else debug
}

从上面可以提炼出以下信息:

  1. 会将launch方法传入的context与CoroutineScope中的context组合起来
  2. 如果combined中没有拦截器,会传入一个默认的拦截器,即Dispatchers.Default,这也解释了为什么我们没有传入拦截器时会有一个默认切换线程的效果

创建一个Continuation

val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)

默认情况下,我们会创建一个StandloneCoroutine 值得注意的是,这个coroutine其实是我们协程体的complete,即成功后的回调,而不是协程体本身 然后调用coroutine.start,这表明协程开始启动了

协程的启动

public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {
    initParentJob()
    start(block, receiver, this)
}

接着调用CoroutineStart的start来启动协程,默认情况下调用的是CoroutineStart.Default

经过层层调用,最后到达了:

internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(receiver: R, completion: Continuation<T>) =
    runSafely(completion) {
        // 外面再包一层 Coroutine
        createCoroutineUnintercepted(receiver, completion)
            // 如果需要,做拦截处理
            .intercepted()
            // 调用 resumeWith 方法      
            .resumeCancellableWith(Result.success(Unit))
    }

这里就是协程启动的核心代码,虽然比较短,却包括3个步骤:

  1. 创建协程体Continuation
  2. 创建拦截 Continuation,即DispatchedContinuation
  3. 执行DispatchedContinuation.resumeWith方法

创建协程体Continuation

调用
createCoroutineUnintercepted,会把我们的协程体即suspend block转换成Continuation,它是SuspendLambda,继承自ContinuationImpl createCoroutineUnintercepted方法在源码中找不到具体实现,不过如果你把协程体代码反编译后就可以看到真正的实现

创建DispatchedContinuation

public actual fun <T> Continuation<T>.intercepted(): Continuation<T> =
    (this as? ContinuationImpl)?.intercepted() ?: this

//ContinuationImpl
public fun intercepted(): Continuation<Any?> =
        intercepted
            ?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
                .also { intercepted = it }     

//CoroutineDispatcher
public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
      DispatchedContinuation(this, continuation)           

从上可以提炼出以下信息

  1. interepted是个扩展方法,最后会调用到ContinuationImpl.intercepted方法
  2. 在intercepted会利用CoroutineContext,获取当前的拦截器
  3. 因为当前的拦截器是CoroutineDispatcher,因此最终会返回一个DispatchedContinuation,我们其实也是利用它实现线程切换的
  4. 我们将协程体的Continuation传入DispatchedContinuation,这里其实用到了装饰器模式,实现功能的增强

这里其实很明显了,通过DispatchedContinuation装饰原有协程,在DispatchedContinuation里通过调度器处理线程切换,不影响原有逻辑,实现功能的增强

拦截处理

    //DispatchedContinuation
    inline fun resumeCancellableWith(
        result: Result<T>,
        noinline onCancellation: ((cause: Throwable) -> Unit)?
    ) {
        val state = result.toState(onCancellation)
        if (dispatcher.isDispatchNeeded(context)) {
            _state = state
            resumeMode = MODE_CANCELLABLE
            dispatcher.dispatch(context, this)
        } else {
            executeUnconfined(state, MODE_CANCELLABLE) {
                if (!resumeCancelled(state)) {
                    resumeUndispatchedWith(result)
                }
            }
        }
    }

上面说到了启动时会调用DispatchedContinuation的resumeCancellableWith方法 这里面做的事也很简单:

  1. 如果需要切换线程,调用dispatcher.dispatcher方法,这里的dispatcher是通过CoroutineConext取出来的
  2. 如果不需要切换线程,直接运行原有线程即可

调度器的具体实现

我们首先明确下,CoroutineDispatcher是通过CoroutineContext取出来的,这也是协程上下文作用的体现 CoroutineDispater官方提供了四种实现:Dispatchers.Main,Dispatchers.IO,Dispatchers.Default,Dispatchers.Unconfined 我们一起简单看下Dispatchers.Main的实现

internal class HandlerContext private constructor(
    private val handler: Handler,
    private val name: String?,
    private val invokeImmediately: Boolean
) : HandlerDispatcher(), Delay {
    public constructor(
        handler: Handler,
        name: String? = null
    ) : this(handler, name, false)

    //...

    override fun dispatch(context: CoroutineContext, block: Runnable) {
        // 利用主线程的 Handler 执行任务
        handler.post(block)
    }
}

可以看到,其实就是用handler切换到了主线程 如果用Dispatcers.IO也是一样的,只不过换成线程池切换了

如上所示,其实就是一个装饰模式

  1. 调用CoroutinDispatcher.dispatch方法切换线程
  2. 切换完成后调用DispatchedTask.run方法,执行真正的协程体

delay是怎样切换线程的?

上面我们介绍了协程线程调度的基本原理与实现,下面我们来回答几个小问题 我们知道delay函数会挂起,然后等待一段时间再恢复。可以想象,这里面应该也涉及到线程的切换,具体是怎么实现的呢?

public suspend fun delay(timeMillis: Long) {
    if (timeMillis <= 0) return // don't delay
    return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
        // if timeMillis == Long.MAX_VALUE then just wait forever like awaitCancellation, don't schedule.
        if (timeMillis < Long.MAX_VALUE) {
            cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
        }
    }
}

internal val CoroutineContext.delay: Delay get() = get(ContinuationInterceptor) as? Delay ?: DefaultDelay

Dealy的代码也很简单,从上面可以提炼出以下信息 delay的切换也是通过拦截器来实现的,内置的拦截器同时也实现了Delay接口 我们来看一个具体实现

internal class HandlerContext private constructor(
    private val handler: Handler,
    private val name: String?,
    private val invokeImmediately: Boolean
) : HandlerDispatcher(), Delay {
    override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
        // 利用主线程的 Handler 延迟执行任务,将完成的 continuation 放在任务中执行
        val block = Runnable {
            with(continuation) { resumeUndispatched(Unit) }
        }
        handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))
        continuation.invokeOnCancellation { handler.removeCallbacks(block) }
    }

    //..
}
  1. 可以看出,其实也是通过handler.postDelayed实现延时效果的
  2. 时间到了之后,再通过resumeUndispatched方法恢复协程
  3. 如果我们用的是Dispatcher.IO,效果也是一样的,不同的就是延时效果是通过切换线程实现的

withContext是怎样切换线程的?

我们在协程体内,可能通过withContext方法简单便捷的切换线程,用同步的方式写异步代码,这也是kotin协程的主要优势之一

    fun test(){
        viewModelScope.launch(Dispatchers.Main) {
            print("1:" + Thread.currentThread().name)
            withContext(Dispatchers.IO){
                delay(1000)
                print("2:" + Thread.currentThread().name)
            }
            print("3:" + Thread.currentThread().name)
        }
    }
    //1,2,3处分别输出main,DefaultDispatcher-worker-1,main

可以看出这段代码做了一个切换线程然后再切换回来的操作,我们可以提出两个问题

  1. withContext是怎样切换线程的?
  2. withContext内的协程体结束后,线程怎样切换回到Dispatchers.Main?
public suspend fun <T> withContext(
    context: CoroutineContext,
    block: suspend CoroutineScope.() -> T
): T {  
    return suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
        // 创建新的context
        val oldContext = uCont.context
        val newContext = oldContext + context
        ....
        //使用新的Dispatcher,覆盖外层
        val coroutine = DispatchedCoroutine(newContext, uCont)
        coroutine.initParentJob()
        //DispatchedCoroutine作为了complete传入
        block.startCoroutineCancellable(coroutine, coroutine)
        coroutine.getResult()
    }
}

private class DispatchedCoroutine<in T>(
    context: CoroutineContext,
    uCont: Continuation<T>
) : ScopeCoroutine<T>(context, uCont) {
 //在complete时会会回调
    override fun afterCompletion(state: Any?) {
        afterResume(state)
    }

    override fun afterResume(state: Any?) {
        //uCont就是父协程,context仍是老版context,因此可以切换回原来的线程上
        uCont.intercepted().resumeCancellableWith(recoverResult(state, uCont))
    }
}

这段代码其实也很简单,可以提炼出以下信息

  1. withContext其实就是一层Api封装,最后调用到了startCoroutineCancellable,这就跟launch后面的流程一样了,我们就不继续跟了
  2. 传入的context会覆盖外层的拦截器并生成一个newContext,因此可以实现线程的切换
  3. DispatchedCoroutine作为complete传入协程体的创建函数中,因此协程体执行完成后会回调到afterCompletion中
  4. DispatchedCoroutine中传入的uCont是父协程,它的拦截器仍是外层的拦截器,因此会切换回原来的线程中

总结

本文主要回答了kotlin协程到底是怎么切换线程的这个问题,并对源码进行了分析 简单来讲主要包括以下步骤:

  1. 向CoroutineContext添加Dispatcher,指定运行的协程
  2. 在启动时将suspend block创建成Continuation,并调用intercepted生成DispatchedContinuation
  3. DispatchedContinuation就是对原有协程的装饰,在这里调用Dispatcher完成线程切换任务后,resume被装饰的协程,就会执行协程体内的代码了

其实kotlin协程就是用装饰器模式实现线程切换的 看起来似乎有不少代码,但是真正的思路其实还是挺简单的,这大概就是设计模式的作用吧

Kotlin 协程调度切换线程是时候解开谜团了 平常大家用的切换到主线程的手段:Activity.runOnUiThread(xx),View.post(xx),Handler.sendMessage(xx) 等简单方式。另外还有一些框架,如AsyncTask、RxJava、线程池等。 它们本质上是借助了Looper+Handler功能。 先看个Demo,在子线程获取学生信息,拿到结果后切换到主线程展示: 我们知道Android UI 刷新是基于事件驱动的,主线程一直尝试从事件队列里拿到待执行的事件,没拿到就等待,拿到后就执行对应的事件。这也是Loope 阅读详情

相关推荐

vh6501测试busoff:CAN总线故障注入实战案例

用vh6501测试BusOff,精准触发CAN总线异常状态,验证ECU在BusOff下的恢复逻辑与容错能力。该实战案例覆盖故障注入流程、波形观测及节点行为分析,是车载网络可靠性测试的关键环节。

weixin_33660045的博客 413

Android中的一次性事件

在Google指南和ViewModel中,单次事件反模式在这个问题上是有意见的。简单来说,一次性事件应该是ViewModel中包含的全面UI状态的一部分,并在UI层进行相应处理。将事件分离到它们自己的流中是不推荐的。但我认为这对于绝大多数情况来说有点过分。它还引入了在适当时间处理/重置状态的手动步骤,从而导致潜在的失败点,否则这些问题根本不存在。有时,你只需要一种简单有效且可单元测试的方法。难道这真的需要这么复杂吗?使用这些方法来处理Android中的一次性事件,只是众多方法中的冰山一角。

chuyouyinghe的专栏 495

IAQ系统防水传感器选型与安装实战:从防护等级到调试排查

在室内空气质量(IAQ)监测系统中,传感器长期暴露于高湿、凝露甚至飞溅水环境,防护设计的好坏直接决定系统稳定性与运维成本。理解防护等级(IP)只是起点,真正决定防水性能的是防水透气膜、壳体密封结构和分体式布局等底层原理。高性能ePTFE膜既能保证气体自由扩散,又能阻隔液态水侵入,配合合理的探头朝向与排水路径,才能有效应对风管、回风箱、厨房排烟道等恶劣点位。掌握传感器接口如RS485 Modbus的防水接线与调试方法,可显著降低通信故障率。从选型、安装到校验,工程实践中的细节往往比标称参数更具价值。本文结合真

weixin_30819085的博客 457

Kotlin Coroutine(二):作用域及取消

一、协程作用域 定义协程必须指定其 CoroutineScope 。CoroutineScope 可以对协程进行追踪,即使协程被挂起也是如此。同调度程序 (Dispatcher) 不同,CoroutineScope 并不运行协程,它只是确保您不会失去对协程的追踪。为了确保所有的协程都会被追踪,Kotlin 不允许在没有使用 CoroutineScope 的情况下启动新的协程。CoroutineScope 可被看作是一个具有超能力的 ExecutorService 的轻量级版本。CoroutineScope

Cupster 3128

基于RK3588的QLED显示终端开发:HDMI输入与USB接口设计实战

嵌入式系统开发中,高清显示与高速接口集成是核心技术挑战。QLED(量子点发光二极管)显示技术以其高色域和高亮度特性,为终端设备提供了卓越的视觉体验。其驱动依赖于强大的图形处理单元(GPU)和精确的时序控制。为实现稳定的视频信号接入,HDMI(高清多媒体接口)协议作为行业标准,要求硬件设计严格遵循差分信号完整性与电磁兼容性(EMC)规范。同时,USB(通用串行总线)接口作为灵活的外设扩展与通信通道,其驱动稳定性与协议解析能力直接影响系统可靠性。这些技术的融合,在智能显示终端、工业人机界面(HMI)及专业监控设

weixin_34414650的博客 336

Kotlin从头开始(一),实战讲述Flutter跨平台框架应用

函数,一般包括两个部分,一部分是函数的声明,另一个部分是函数的实现。上述写的getString方法中,fun getString (input: String) : String是函数的声明,括号中的就是函数的实现。 那么Kotlin中, 怎么写一个函数的声明呢? var getString : (String) -> String 是不是看的一头雾水,如果写出对应的Java函数的话,应该是如下的样子: public String getString(String inputStr) 对比不难发现,k

m0_66264655的博客 718

当“贵阳制造”遇见“AI大脑”——一场席卷西南的智造风暴

培训结束后的自由交流环节,原本预计。

cjx751126的博客 253

海康/大华设备如何接入GB28181平台?最新2022版互联避坑指南

本文详细解析了海康/大华设备接入GB28181平台的最新2022版互联避坑指南,涵盖参数配置、实时视音频点播协议交互及典型故障排查。重点介绍了GB/T28181-2022标准下的设备编码规范、SIP服务器配置及媒体流保活机制,帮助工程团队高效解决设备离线和媒体流中断等问题。

weixin_30733003的博客 690

Windows下STLink驱动安装注册表问题修复实战

针对Windows系统下stlink驱动安装常遇到的注册表错误,提供详细修复步骤与实战经验,解决设备无法识别等问题,确保stlink驱动安装稳定可靠。

weixin_30700095的博客 360

【AI Agent系列】【MetaGPT】9. 一句话订阅专属信息 - 订阅智能体进阶,实现一个更通用的订阅智能体(2)

订阅智能体进阶(2)。不用爬虫,直接提取网页信息。从RunSubscription分离出AddSubscriptionTask的action,并且让SubscriptionRunner单独运行。

同学小张的博客 1589

【AI大模型应用开发】【LlamaIndex教程】2. 存储模块:如何在 LlamaIndex 中使用自定义的向量数据库?(附代码)

前面文章两行代码就实现了文档的切分和向量化存储以及持久化存储。如果我们想用自定义的向量化数据库呢?

同学小张的博客 2119

ECRS分析原则:从流程优化到效率重塑的系统化思维框架

流程优化是提升组织效率与竞争力的核心工程,其本质在于系统化地识别并消除浪费。ECRS分析原则作为一套经典且强大的思维框架,为此提供了清晰的方法论路径。该原则遵循取消、合并、重排、简化的逻辑顺序,引导人们跳出局部优化的惯性,从流程的必要性、结构、顺序及操作细节等根本层面进行审视与重塑。其技术价值在于将模糊的效率改进转化为可执行、可验证的结构化行动,广泛应用于制造业生产线平衡、服务业流程再造乃至个人时间管理等场景。例如,通过合并关联任务减少交接等待,或利用防错法简化操作以降低对人员经验的依赖,都是ECRS原则驱

weixin_33875839的博客 428

智能座舱SoC平台横评:SA8155P、SA8255P、SA8295P、MT8678、MT8628 谁是下代车机首选?

高通SA8295P以PC级架构、强悍AI算力,代表安卓阵营迈向“本地大模型+空间计算”阶段;联发科MT8678则凭借低功耗 + 高带宽,成为副驾多媒体/副屏智能化的性价比之王。在座舱设计中,SoC平台决定了你能给用户什么样的交互方式,选型时切忌只看跑分,而要回归“体验-热设计-平台生态”的三角平衡。

weixin_43199439的博客 3359

MES项目成功三要素:业务目标、数据基础与组织变革

制造执行系统(MES)作为连接企业计划层与控制层的关键信息系统,其核心价值在于实现生产过程的透明化与数据驱动决策。系统通过集成数据采集、流程管理和实时监控等技术原理,将生产现场的“黑箱”转化为可视、可控、可优化的数字空间,从而支撑精益生产和智能制造转型。在工程实践中,MES的成功应用远不止于技术部署,更依赖于前期的扎实准备。项目启动前,必须协同定义清晰可衡量的业务目标,这解决了“为什么做”的方向问题;需要夯实数据基础,包括物料、设备等主数据标准化与实时数据采集方案,这是系统运行的“血液”;还需推动主动的组织

weixin_30326741的博客 386

VMware16虚拟机:安装Windows10系统---超详细教程

VMware16虚拟机:安装Windows10系统---超详细教程

MQ0522的博客 3万+

Matlab之App Designer 如何进行三维图形绘制教程

基于Matlab中的App Designer设计可视化界面并进行数据导入及3D绘图,可以直观的在可视化界面上展示分析结果,方便用户进行图形的操作。本文采用案例讲解的方式,详细介绍了如何采用Matlab App Designer建立数据3D绘图的GUI可视化界面的方法和详细的过程步骤,并介绍了导入数据进行计算时需要注意的相关事项,同时提供了详细的代码,以便读者进行学习和操作。

m0_59012156的博客 2067

四轴飞行器兴趣小组聚会策划:从主题设计到实战调参的全流程指南

无人机技术,特别是多旋翼飞行器,其核心在于飞控系统与动力系统的协同。飞控算法通过PID等控制原理,实时处理传感器数据,调整电机转速,从而实现稳定的姿态与轨迹控制。这项技术的价值在于将理论控制模型转化为精准的飞行性能,是无人机从“能飞”到“飞得好”的关键。在工程实践中,无论是竞速FPV还是行业应用,都离不开对飞控参数、动力匹配和图传系统的深度调校。这些调校工作往往涉及复杂的软硬件交互,例如连接飞控进行参数调试、解决电机排序与振动问题等,是爱好者社群交流与实践的重点。本文以一场四轴飞行器兴趣小组的深度聚会为例,

weixin_33797791的博客 343

[案例四] 智能填写属性工具(支持装配组件还有建模实体属性的批量创建、编辑)

本次案例主要是对上次导出自动导出BOM的一个前处理,要想导出属性,首先的有属性。于是本着学习的态度进行制作,可能有些功能有点别扭(支持装配环境下组件属性的批量填写,支持建模环境下的实体(不包括装配组件实体)——为了减负我把其过滤掉了,一般没人会往装配体里添加进行建模吧,但是不免有奇葩,哈哈哈)。

weixin_47753171的博客 1982

【深入理解Kotlin协程】协程调度器Dispatchers源码追踪扒皮

Android中的Kotlin协程调度器主要有四种:Dispatchers.Main、Dispatchers.Default、Dispatchers.IO、Dispatchers.Unconfined,第四种平时开发应该一般不会用到,所以主要了解一下前3种的实现。

川峰的博客 1652

“华为杯”研究生数学建模竞赛2020年-【华为杯】D题:无人机集群协同对抗(附获奖论文和MATLAB代码实现)

1.问题重述摘 要:本文针对非线性约束条件下红蓝双方无人机集群协同对抗的最优规划问题,结合贪婪队形、非线性规划、内点法、蒙特卡洛方法和全联立正交配置有限元法,构建了无人机集群协同对抗推演模型。,构建了。我们综合考虑了,和。首先分析红方在集群约束下的贪婪队形和拦截策略,然后我们基于内点法联合MATLAB和AMPL编程求解蓝方突防的非线性规划解,最后基于蒙特卡洛法对整个蓝方突防过程进行推演,得到突防概率图。通过推演,我们提出了两块的区域作为所求区域,并对蓝方的最优突防策略进行分析。

getusushu的博客 1553

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

Kotlin-如何创建一个好用的协程作用域

前言 kotlin中使用协程,是一定要跟协程作用域一起配合使用的,否则可能协程的生命周期无法被准确控制,造成内存泄漏或其他问题. 我们一般在安卓项目中使用协程作用域,可能会在BaseActtivity中new 一个MainScope(),并在onDestory时cancel掉,或者只在ViewModel中使用viewModelScope,然后会在ViewModel的onClose中自动cancel掉. 但我们可能不只需要这些效果,比如在协程作用域中拿到Context或Activity,或者需要统一的捕

lt的博客 2336

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

解密 Kotlin 中的隐藏调度器:Dispatchers.Main.immediate

摘要:本文介绍了 Kotlin 协程中不为人知但实用的 Dispatchers.Main.immediate 调度器。与普通 Dispatchers.Main 不同,当已在主线程时它会立即执行任务而非加入消息队列。文章通过代码对比展示了 viewModelScope(默认使用该调度器)与普通协程的差异,解释了其实现原理和适用场景,如减少线程切换、确保执行顺序等。同时提供了手动使用方法和注意事项,帮助开发者编写更高效的协程代码。

居然是阿松的博客 1261

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

Kotlin协程到底是怎么切换线程的?你是否知晓?

好文推荐: 作者:RicardoMJiang 前言 之前对协程做了一个简单的介绍,回答了协程到底是什么的问题,感兴趣的同学可以了解下:【带着问题学】协程到底是什么? 通过上文,我们了解了以下内容 1.kotlin协程本质上对线程池的封装 2.kotlin协程可以用同步方式写异步代码,自动实现对线程切换的管理 这就引出了本文的主要内容,kotlin协程到底是怎么切换线程的? 具体内容如下: 1. 前置知识 1.1 CoroutineScope到底是什么? CoroutineScope即协程运行的作用域,.

鸿蒙开发知识记录 4288

Jetpack实践指南:lifecycle与协程的“猫腻”事

本篇文章主要是讲解如何使用lifecycle创建协程 、源码解析以及lifecycle在协程中的应用。

bugyinyin的博客 1734

在 Android 开发中使用协程 | 背景介绍

本文是介绍 Android 协程系列中的第一部分,主要会介绍协程是如何工作的,它们主要解决什么问题。协程用来解决什么问题?Kotlin 中的协程提供了一种全新处理并发的方式,您可以在 ...

谷歌开发者 955
上一篇: 一杯茶的功夫就把日志搜索引擎性能调优了
下一篇: 数万字总结,建议慢慢看,数据库安全之MongoDB渗透
JavaShark
博客等级 码龄4年 184粉丝 438原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值