Kotlin Coroutine的超时处理

这2天在跟着kotlin的官方文档练习阅读,复习协程.看到Cancellation and timeouts的时候,觉得自己之前的超时处理逻辑如果切换成withTimeout将会更加简洁.果然官方教程是最好的教程.先看看我之前的实现:

 val timeOut = lifecycleScope.async {
     delay(40 * 1000)
     LogUtils.e(TAG, "上传超时 onTimeout: 45s")
     false
 }
 val uploadResult = lifecycleScope.async {
     suspendCoroutine<Boolean> {
         upload(file,file.name){ key, info, response ->
		                    if (info?.isOK == true) {
		                        LogUtils.d(TAG, "上传成功: ${response.toString()}")
		                        it.resume(true)
		                    } else {
		                        LogUtils.e(TAG, "上传失败:code=${info.statusCode}  error = ${info.error}")
		                        it.resume(false)
		                    }
		            }
		    }
		}
if (select {
            timeOut.onAwait { it }
            uploadResult.onAwait { it }
        }) {//前面2个async哪一个先返回就取哪一个的值
  	//上传成功
    timeOut.cancel()
    uploadAndDelete(item)
}else{
  //超时或失败
    timeOut.cancel()
    uploadResult.cancel()
}
LogUtils.v(TAG, "onComplete")

这样当然是可以达到目的的,但是感觉不够简洁.也没有实际用过withTimeout,结果一番改造后,超时失效了,测试的代码也翻了车,改了好几个版本都不行,先来看看最后一版测试的代码:

fun main() = runBlocking {
   launch {
        val succ = withTimeoutOrNull(3000){
          	//delay(4000)
            val result = suspendCoroutine<Boolean> {
                //模拟进行上传操作一直不调用resume,触发超时
				//it.resume(true)
            }
            println("result $result")
            return@withTimeoutOrNull result
        }
        if (succ == true) {
            println("上传成功 删除文件")
        } else {
            println("上传超时")
        }
    }
    println("onComplete")
}

---------------------运行输出---------------------
> Task :MainKt.main()
onComplete

结果就输出了onComplete然后无尽挂起了,也不返回超时,程序也不结束.但是放开delay(4000)手动延迟后,又返回超时了,百思不得其解.一度对自己的使用姿势产生了怀疑.经过使用async{},whitContext{}等等wrap,awite(),jion()等等调用尝试后,终于发现了问题所在:

前面使用了suspendCoroutine来将一个api的回调转换成协程.之前那么用是没有问题的,但是现在被withTimeoutOrNull包裹使用的话,就会有问题了,因为 suspendCoroutine是不可取消的 .有点像被withContext(NonCancellable) {...}包裹的代码对其调用cancle是无效的.详见:Run non-cancellable block.其实只要对比一下suspendCoroutinesuspendCancellableCoroutine的代码就能发现一些端倪:

@SinceKotlin("1.3")
@InlineOnly
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())//这里创建了一个Continuation的实现类
        block(safe)
        safe.getOrThrow()
    }
}


public suspend inline fun <T> suspendCancellableCoroutine(
    crossinline block: (CancellableContinuation<T>) -> Unit
): T =
    suspendCoroutineUninterceptedOrReturn { uCont ->
        val cancellable = CancellableContinuationImpl(uCont.intercepted(), resumeMode = MODE_CANCELLABLE)//这里创建了Continuation的另外一个实现类,从类名和这个类的签名可知,这是可取消的
        /*
         * 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()
    }

因为suspendCoroutine的实现不支持取消,所以即使超时了,这个挂起点也将一直处于挂起状态,后续的代码自然就得不到执行了.找到了问题修改suspendCoroutinesuspendCancellableCoroutine问题就得到了解决,最终的实现是:

lifecycleScope.launch {
    val success = withTimeoutOrNull(40*1000){
        return@withTimeoutOrNull suspendCancellableCoroutine<Boolean> {
          upload(file,file.name){ key, info, response ->
              if (info?.isOK == true) {
                  LogUtils.d(TAG, "上传成功: ${response.toString()}")
                  it.resume(true)
              } else {
                  LogUtils.e(TAG, "上传失败:code=${info.statusCode}  error = ${info.error}")
                  it.resume(false)
              }
					}
   		}
    when(success){
        null->LogUtils.e(TAG, "上传超时 onTimeout: 40s")
        true->uploadAndDelete(item)
    }
    LogUtils.v(TAG, "onComplete")
}

这里使用withTimeoutOrNullwithTimeout更加的符合我的需要,它们的区别是前者在超时时将返回null,后者将抛出异常,需要try{}catch.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值