2024年关于Kotlin协程的一些应用_kotlin 协程在其他地方使用(3),2024HarmonyOS鸿蒙面试总结

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!


img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

    // ...
}

override fun onFailure(t: Throwable) {
  // ...
}

})
}


如果使用 suspendCancellableCoroutine 的话就变成这样:



private suspend fun requestWithSuspend(): String {
return suspendCancellableCoroutine { cancellableContinuation->
request(object : ICallBack {
override fun onSuccess(data: String) {
cancellableContinuation.resume(data)
}

            override fun onFailure(t: Throwable) {
                cancellableContinuation.resumeWithException(t)
            }
        })
    }
}

同样需要注意是,这是协程的写法,方法也标明了 suspend ,所以只能在协程中使用。


其实为什么Retrofit的请求看似把异步的网络请求用成了同步一样,Retrofit的内部也是同样的处理。


Retrofit最终的处理逻辑在此:KotlinExtensions.awaitResponse


![](https://img-blog.csdnimg.cn/658d1f89c77c48ca84d224b45a5438fb.png)


所以我们照着Retrofit学就行了。


#### 三、Kotlin协程分发器


有没有同学全部用 Dispatchers.IO 切换线程调度的。


Dispatchers.IO / Dispatchers.Default 的异同:


两者都是协程分发器,Dispatchers.IO 侧重于任务本身是阻塞型的,比如文件、数据库、网络等操作等。并不那么占用CPU


而Dispatchers.Default 则偏向那些可能会长时间占用CPU的任务。比如人脸特征提取,图片压缩处理,视频的合成等。


他们的线程池的实现也是不同的


协程线程池在设计的时候,针对两者在线程的调度策略上有所不同。


所有任务分成纯CPU任务和非纯CPU任务两种,对应着核心线程和非核心线程。


入队的逻辑是 Dispatchers.IO 的任务放入 globalBlockingQueue 队列,而 Dispatchers.Default 的任务放入的是 globalBlockingQueue 队列。


所有线程在执行前都先尝试成为核心线程,核心线程可以从两种任务中任意选择执行,非核心线程只能执行非纯CPU任务。核心线程如果选择执行非纯CPU任务会变成非核心线程。


所以真的有人从来没用过 Dispatchers.Default 吗?


#### 四、使用协程有什么好处?怎么用?


看到过网上的一些Java线程池比协程线程池执行逻辑更快的文章,其实意义不大,协程最大的优势是会更加的方便,可以很方便的把一些碎片化的方法加入协程,同时它可以去掉回调地狱还能更加方便的实现并发与排队执行的效果。


比如这样的一个场景,在主线程计算薪水,我们根据时薪与工作时长计算总共的薪水,内部有复杂的判断,是否是签约员工,是否迟到了,迟到了扣钱,扣除五险一金,连续工作的奖励,推荐的奖励,顾客打赏,等等一系列的复杂逻辑,我们就可以随意加入协程中。



private fun calculateSalary(): String {
    // 省略100行代码
    return "1000"
}

private suspend fun calculateSalary2() = withContext(Dispatchers.Default) {
    // 省略100行代码
    "2000"
}

private suspend fun calculateSalary3() = coroutineScope {
    // 省略100行代码
    "3000"
}

**下面看看代码的优化:**



class CalculateFaceUtil private constructor() : CoroutineScope by MainScope() {

//... 单例

/\*\*

* 计算并找到最匹对的人脸信息
*
* 使用协程异步的并发的双端遍历查询最大值
*/
fun getTopFace(
list: List,
faceEngine: FaceEngine,
faceFeature: FaceFeature,
action: (similar: Float, index: Int) -> Unit
) {

  // 、、、其他逻辑

   val middlePosition = list.size / 2

    launch(Dispatchers.IO) {

        val topface1 = async {
            val tempFaceFeature = FaceFeature()
            val faceSimilar = FaceSimilar()
            var maxSimilar = 0f
            var maxSimilarIndex = -1

            for (i in 0 until middlePosition) {
                tempFaceFeature.featureData = list[i].featureData
                //调用SDK比对两个 FaceFeature 人脸特征,返回相似度
                faceEngine.compareFaceFeature(faceFeature, tempFaceFeature, faceSimilar)
                //拿到相似度的对象,获取得分(每一次都会全部遍历,如果有相同的图片还是会取到最后的)
                if (faceSimilar.score > maxSimilar) {
                    maxSimilar = faceSimilar.score
                    maxSimilarIndex = i
                }
            }

            TopFace(maxSimilar, maxSimilarIndex)
        }

        val topface2 = async {
            val tempFaceFeature = FaceFeature()
            val faceSimilar = FaceSimilar()
            var maxSimilar = 0f
            var maxSimilarIndex = -1

            for (i in middlePosition until list.size) {
                tempFaceFeature.featureData = list[i].featureData
                //调用SDK比对两个 FaceFeature 人脸特征,返回相似度
                faceEngine.compareFaceFeature(faceFeature, tempFaceFeature, faceSimilar)
                //拿到相似度的对象,获取得分(每一次都会全部遍历,如果有相同的图片还是会取到最后的)
                if (faceSimilar.score > maxSimilar) {
                    maxSimilar = faceSimilar.score
                    maxSimilarIndex = i
                }
            }

            TopFace(maxSimilar, maxSimilarIndex)
        }

        //并发查找并找到最大值
        val face1 = topface1.await()
        val face2 = topface2.await()

        if (face1 != null && face2 != null) {
            //回调到主线程
            withContext(Dispatchers.Main) {
                //优先返回后面的数据
                if (face2.similar > face1.similar) {
                    action(face2.similar, face2.index)
                } else {
                    action(face1.similar, face1.index)
                }
            }

        }
    }

}

}


场景:ViewModel中调用这个工具类,查找较大集合中最匹配的人脸,使用头尾双端遍历找到最大值。


我们以这一个使用场景为例,逻辑没问题,但是协程的使用有优化的空间。


1.计算最好使用 Dispatchers.Default , 这是小问题。 2.viewModel中viewModelScope协程作用域中调用全局的协程作用域,这…感觉不太好,推荐使用下面的方式,继承父布局的协程作用域。



suspend fun getTopFace() = coroutineScope {

    async(Dispatchers.Default) {

    }

    async(Dispatchers.Default) {

    }

    // 。。。
}

3.使用高阶函数回调,如果是协程中最好是可以铺平回调



suspend fun getTopFace(
    list: List<FaceRegisterInfo>,
    faceEngine: FaceEngine,
    faceFeature: FaceFeature,
): TopFace = suspendCoroutine { continuation ->

    async(Dispatchers.Default) {

    }

    async(Dispatchers.Default) {

    }

    // 。。。
    continuation.resume(TopFace(maxSimilar, maxSimilarIndex))
}

之前的viewModel中使用:



private fun searchFace(
frFace: FaceFeature, requestId: Int,
orignData: ByteArray?, faceInfo: FaceInfo?, width: Int, height: Int
) {

    viewModelScope.launch {

        //通过FaceServer找到最匹配的人脸
        CalculateFaceUtil.getInstance().getTopFace(frFace) { compareResult ->

           // 。。。逻辑
        }
    }
}

在回调里面写逻辑,后面的逻辑就破坏了协程作用域,那么又要使用工具类开启一个新的协程,这样就很不好。


现在的viewModel中使用:



private fun searchFace(
frFace: FaceFeature, requestId: Int,
orignData: ByteArray?, faceInfo: FaceInfo?, width: Int, height: Int
) {

    viewModelScope.launch {

       //通过FaceServer找到最匹配的人脸
       val topFace = CalculateFaceUtil.getInstance().getTopFace(frFace) 

        // 。。。逻辑

        withContext(Dispatchers.IO){
            //上传到网络逻辑
        }

    }
}

修改之后就可以直接在一个作用域中切换线程的调度。


还有一个比较典型的例子就是网络请求用的很多的协程处理类,很多人喜欢把网络请求的结果再封装一层,指定成功或失败。例如:



sealed class OkResult {

data class Success<out T : Any>(val data: T) : OkResult<T>()
data class Error(val exception: Exception) : OkResult<Nothing>()

//检测成功与失败
fun checkResult(success: (T) -> Unit, error: (String?) -> Unit) {
    if (this is Success) {
        success(data)
    } else if (this is Error) {
        error(exception.message)
    }
}

//只是检测成功
fun checkSuccess(success: (T) -> Unit) {
    if (this is Success) {
        success(data)
    }
}

}


由于也是使用高阶函数回调的,那么就会遇到同样的问题。



viewModelScope.launch {

val result = mSyncRepository.syncAttendance(attendance, token)
result.checkSuccess {
    //数据库操作
    AttendanceDBHelper.updateAttendance(attendance)
}

}


场景是网络请求提交考勤数据,然后保存到数据库里,那么这样的方法数据库的操作就只能在主线程了,不能切换线程了,所以推荐使用去除回调的方式:



//检查并返回是成功还是失败(在协程中使用直接返回铺平回调)
suspend fun isSuccess(): Boolean {
    return suspendCoroutine { continuation ->
        continuation.resume(this is Success)
    }
}

修改之后



val result = mSyncRepository.syncAttendance(attendance, token)
if(result.isSuccess()){
//数据库操作
withContext(Dispatchers.IO){
AttendanceDBHelper.updateAttendance(attendance)
}
}



**深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

![](https://img-blog.csdnimg.cn/direct/743b668910224b259a5ffe804fa6d0db.png)
![img](https://img-blog.csdnimg.cn/img_convert/fe46e832bcf90a01277eed7c242a18f0.png)
![img](https://img-blog.csdnimg.cn/img_convert/e43ffa462af4f2572321a132eeb31ae1.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618636735)**

效果低效又漫长,而且极易碰到天花板技术停滞不前!**

![](https://img-blog.csdnimg.cn/direct/743b668910224b259a5ffe804fa6d0db.png)
[外链图片转存中...(img-k1PsgOxj-1715647558371)]
[外链图片转存中...(img-cVXmmnem-1715647558371)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618636735)**

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值