網站首頁 編程語言 正文
前言
我們再來一期關于kotlin協程的故事,我們都知道在Coroutine沒有出來之前,我們對于異步結果的處理都是采用回調的方式進行,一方面回調層次過多的話,容易導致“回調地獄”,另一方法也比較難以維護。當然,我們并不是否定了回調本身,回調本身同時也是具備很多優點的,比如符合代碼閱讀邏輯,同時回調本身也是比較可控的。這一期呢,我們就是來聊一下,如何把回調的寫法變成suspend函數,同時如何把suspend函數變成回調,從而讓我們更加了解kotlin協程背后的故事
回調變成suspend函數
來一個回調
我們以一個回調函數作為例子,當我們normalCallBack在一個子線程中做一些處理,比如耗時函數,做完就會通過MyCallBack回調onCallBack,這里返回了一個Int類型,如下:
var myCallBack:MyCallBack?= null
interface MyCallBack{
fun onCallBack(result: Int)
}
fun normalCallBack(){
thread {
// 比如做一些事情
myCallBack?.onCallBack(1)
}
}
轉化為suspend函數
此時我們可以通過suspendCoroutine函數,內部其實是通過創建了一個SafeContinuation并放到了我們suspend函數本身(block本身)啟動了一個協程,我們之前在聊一聊Kotlin協程"低級"api 這篇文章介紹過
public suspend inline fun <T> suspendCoroutine(crossinline block: (Continuation<T>) -> Unit): T {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return suspendCoroutineUninterceptedOrReturn { c: Continuation<T> ->
val safe = SafeContinuation(c.intercepted())
block(safe)
safe.getOrThrow()
}
}
這時候我們就可以直接寫為,從而將回調消除,變成了一個suspend函數。
suspend fun mySuspend() = suspendCoroutine<Int> {
thread {
// 比如做一些事情
it.resume(1)
}
}
當然,如果我們想要支持一下外部取消,比如當前頁面銷毀時,發起的網絡請求自然也就不需要再請求了,就可以通過suspendCancellableCoroutine創建,里面的Continuation對象就從SafeContinuation(見上文)變成了CancellableContinuation,變成了CancellableContinuation有一個invokeOnCancellation方便,支持在協程體被銷毀時的邏輯。
public suspend inline fun <T> suspendCancellableCoroutine(
crossinline block: (CancellableContinuation<T>) -> Unit
): T =
suspendCoroutineUninterceptedOrReturn { uCont ->
val cancellable = CancellableContinuationImpl(uCont.intercepted(), resumeMode = MODE_CANCELLABLE)
/*
* For non-atomic cancellation we setup parent-child relationship immediately
* in case when `block` blocks the current thread (e.g. Rx2 with trampoline scheduler), but
* properly supports cancellation.
*/
cancellable.initCancellability()
block(cancellable)
cancellable.getResult()
}
此時我們就可以寫出以下代碼
suspend fun mySuspend2() = suspendCancellableCoroutine<Int> {
thread {
// 比如做一些事情
it.resume(1)
}
it.invokeOnCancellation {
// 取消邏輯
}
}
suspend函數變成回調
見到了回調如何變成suspend函數,那么我們反過來呢?有沒有辦法?當然有啦!當時suspend函數中有很多種區分,我們一一區分一下
//直接返回的suspend函數
suspend fun myNoSuspendFunc():Int{
return 1
}
//調用suspendCoroutine后直接resume的suspend函數
suspend fun myNoSuspendFunc() = suspendCoroutine<Int> {
continuation ->
continuation.resume(1)
}
//調用suspendCoroutine后異步執行的suspend函數(這里異步可以是單線程也可以是多線程,跟線程本身無關,只要是異步就會觸發掛起)
suspend fun myRealSuspendFunc() = suspendCoroutine<Int> {
thread {
Thread.sleep(300)
it.resume(2)
}
那么我們來想一下,這里真正發起掛起的函數是哪個?通過代碼其實我們可以猜到,真正掛起的函數只有最后一個myRealSuspendFunc,其他都不是真正的掛起,這里的掛起是什么意思呢?我們從協程的狀態就可以知道,當前處于CoroutineSingletons.COROUTINE_SUSPENDED時,就是掛起狀態。我們回歸一下,一個suspend函數有哪幾種情況
這里的1,2,3就分別對應著上文demo中的例子
- 直接返回結果,不需要進入狀態機判斷,因為本身就沒有啟動協程
- 進入了協程,但是不需要進行SUSPEND狀態就已經有了結果,所以直接返回了結果
- 進入了SUSPEND狀態,之后才能獲取結果
這里我們就不貼出來源碼了,感興趣可自己看Coroutine的實現,這里我們要明確一個概念,一個Suspend函數的運行機制,其實并不依靠了協程本身。
對應代碼表現就是,這個函數的返回結果可能就是直接返回結果本身,另一種就是通過回調本身通知外部(這里我們還會以例子說明)
suspend函數轉換為回調
這里有兩種情況,我們分別以kotlin代碼跟java代碼表示:
kotlin代碼
由于kotlin可以直接通過suspend的擴展函數startCoroutine啟動一個協程,
fun myRealSuspendCallBack(){
::myRealSuspendFunc.startCoroutine(object :Continuation<Int>{
當前環境
override val context: CoroutineContext
get() = Dispatchers.IO
結果
override fun resumeWith(result: Result<Int>) {
if(result.isSuccess){
myCallBack?.onCallBack(result.getOrDefault(0))
}
}
})
}
其中Result就是一個內聯類,屬于kotlin編譯器添加的裝飾類,在這里我們無論是1,2,3的情況,都可以在resumeWith 中獲取到結果,在這里通過callback回調即可
@JvmInline
public value class Result<out T> @PublishedApi internal constructor(
@PublishedApi
internal val value: Any?
) : Serializable {
Java代碼
這里我們更正一個誤區,就是suspend函數只能在kotlin中使用/Coroutine協程只能在kotlin中使用,這個其實是錯誤的,java代碼也能夠調起協程,只不過麻煩了一點,至少官方是沒有禁止的。 比如我們需要調用startCoroutine,可直接調用
ContinuationKt.startCoroutine();
當然,我們也能夠直接調用suspend函數
Object result = CallBack.INSTANCE.myRealSuspendFunc(new Continuation<Integer>() {
@NonNull
@Override
public CoroutineContext getContext() {
//這里啟動的環境其實協程沒有用到,讀者們可以思考一下為什么!這里就當一個謎題啦!可以在評論區說出你的想法(我會在評論區解答)
return (CoroutineContext) Dispatchers.getIO();
//return EmptyCoroutineContext.INSTANCE;
}
@Override
public void resumeWith(@NonNull Object o) {
情況3
Log.e("hello","resumeWith result is "+ o +" is main "+ (Looper.myLooper() == Looper.getMainLooper()));
// 回調處理即可
myCallBack?.onCallBack(result)
}
});
if(result != IntrinsicsKt.getCOROUTINE_SUSPENDED()){
情況1,2
Log.e("hello","func result is "+ result);
// 回調處理即可
myCallBack?.onCallBack(result)
}
這里我們需要注意的是,這里java代碼比kotlin多了一個判斷,同時resumeWith的參數不再是Result,而是一個Object
if(result != IntrinsicsKt.getCOROUTINE_SUSPENDED()){
}
這里脫去了kotlin給我們添加的各種外殼,其實這就是真正的對于suspend結果的處理(只不過kotlin幫我們包了一層)
我們上文說過,suspend函數對應的三種情況,這里的1,2都是直接返回結果的,因為沒有走到SUSPEND狀態(IntrinsicsKt.getCOROUTINE_SUSPENDED())這里需要讀者好好閱讀上文,因此 result != IntrinsicsKt.getCOROUTINE_SUSPENDED(),就會直接走到這里,我們就直接拿到了結果
if(result != IntrinsicsKt.getCOROUTINE_SUSPENDED()){
}
如果屬于情況3,那么這里的result就不再是一個結果,而是當前協程的狀態標記罷了,此時當協程完成執行的時候(調用resume的時候),就會回調到resumeWith,這里的Object類型o才是經過SUSPEND狀態的結果!
總結
經過我們suspend跟回調的互相狀態,能夠明白了suspend背后的邏輯與掛起的細節,希望能幫到你
原文鏈接:https://juejin.cn/post/7175752374458253373
相關推薦
- 2022-04-20 C語言函數棧幀的創建和銷毀詳解_C 語言
- 2022-11-25 Docker?制作tomcat鏡像并部署項目的步驟_docker
- 2022-11-12 sass在react中的基本使用(實例詳解)_React
- 2022-05-21 Python實現歸一化算法詳情_python
- 2022-06-23 python入門語句基礎之if語句、while語句_python
- 2022-09-14 iOS開發UI之弧形文字_IOS
- 2022-11-30 Python利用yarl實現輕松操作url_python
- 2024-03-17 樹莓派無桌面配置WiFi連接
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支