探索 Kotlin 协程 withTimeout 原理

概述

接下来我会围绕一个问题探讨一下 withTimeout 的实现,这个问题就是:为什么 Thread.sleep 在 withTimeout 中无效?

1. 协程的取消协作机制

在 Kotlin 的官方文档中,说到了协程的取消是协作完成的,这也是为什么协程叫协程,协程的代码必须进行协作才能被取消,而 withTimeout 也是通过这套取消协作机制完成的,比如下面的例子中,在打印第四个数字的时候,就在执行超时后抛出了异常。

withTimeout(1300L) {
    repeat(1000) { i ->
        println("I'm sleeping $i ...")
        delay(500L)
    }
}

上面这段代码的执行结果如下:

I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
Exception in thread "main" kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1300 ms

withTimeout 抛出的 TimeoutCancellationExceptionCancellationException 的子类,由于取消只是一个异常,所有的资源都可以按照通常的方式关闭。如果需要在任何类型的超时发生时执行某些额外的操作,则可以将代码包装在 try{...} catch(){...} 块中。或者可以使用 withTimeoutOrNull 函数,该函数类似于 withTimeout,但在超时时返回 null 而不是抛出异常:

val result = withTimeoutOrNull(1300L) {
    repeat(1000) { i ->
        println("I'm sleeping $i ...")
        delay(500L)
    }
    "Done" // will get cancelled before it produces this result
}
println("Result is $result")
测试代码 1 :withTimeout() 与 delay()
class ExampleUnitTest {

    @Test
    fun testWithTimeoutAndDelay() {
        val latch = CountDownLatch(1)
        GlobalScope.launch {
            kotlin.runCatching {
                // 20 秒内没有执行完的话就抛出超时取消异常
                withTimeout(20 * 1000) {
                    // 延迟 30 秒
                    delay(30 * 1000)
                    println("after delay")
                    latch.countDown()
                }
            }.onFailure { 
                println("failed: $it")
            }
        }
        latch.await()
    }
}

上面这段代码对应的 Java 代码如下:

public final class ExampleUnitTest {
   @Test
   public final void testWithTimeoutAndDelay() {
      final Ref.ObjectRef latch = new Ref.ObjectRef();
      latch.element = new CountDownLatch(1);
     
      // launch 代码块
      BuildersKt.launch$default((CoroutineScope)GlobalScope.INSTANCE, (CoroutineContext)Dispatchers.getUnconfined(), (CoroutineStart)null, (Function2)(new Function2((Continuation)null) {
         int label;

         @Nullable
         public final Object invokeSuspend(@NotNull Object $result) {
            Throwable var3;
            Object var12;
            Throwable var10000;
            label42: {
               Result.Companion var13;
               label41: {
                  Object var8 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
                  boolean var2;
                  boolean var10001;
                  switch (this.label) {
                     case 0:
                        // 如果 result 是 Result.Failure ,则抛出 Failure 的异常值
                        ResultKt.throwOnFailure($result);

                        Object var14;
                        try {
                           var13 = Result.Companion;
                           var2 = false;
                           // 创建并执行 withTimeout 代码块
                           Function2 var15 = (Function2)(new ExampleUnitTest$testWithTimeoutAndDelay$1$invokeSuspend$$inlined$runCatching$lambda$1((Continuation)null, this));
                           this.label = 1;
                           var14 = TimeoutKt.withTimeout(20000L, var15, this);
                        } catch (Throwable var10) {
                           var10000 = var10;
                           var10001 = false;
                           break label41;
                        }

                        if (var14 == var8) {
                           return var8;
                        }
                        break;
                     case 1:
                        var2 = false;

                        try {
                           ResultKt.throwOnFailure($result);
                           break;
                        } catch (Throwable var11) {
                           var10000 = var11;
                           var10001 = false;
                           break label41;
                        }
                     default:
                        throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
                  }

                  try {
                     var12 = Result.constructor-impl(Unit.INSTANCE);
                     break label42;
                  } catch (Throwable var9) {
                     var10000 = var9;
                     var10001 = false;
                  }
               }

               var3 = var10000;
               var13 = Result.Companion;
               var12 = Result.constructor-impl(ResultKt.createFailure(var3));
            }

            var10000 = Result.exceptionOrNull-impl(var12);
            if (var10000 != null) {
               var3 = var10000;
               int var6 = false;
               String var7 = "failed: " + var3;
               System.out.println(var7);
            }

            return Unit.INSTANCE;
         }

         @NotNull
         public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
            Intrinsics.checkNotNullParameter(completion, "completion");
            Function2 var3 = new <anonymous constructor>(completion);
            return var3;
         }

         public final Object invoke(Object var1, Object var2) {
            return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);
         }
      }), 2, (Object)null);
      ((CountDownLatch)latch.element).await();
   }
}
// ExampleUnitTest$testWithTimeoutAndDelay$1$invokeSuspend$$inlined$runCatching$lambda$1.java
// ...
@DebugMetadata(
   f = "ExampleUnitTest.kt",
   l = {24},
   i = {},
   s = {},
   n = {},
   m = "invokeSuspend",
   c = "com.example.myapplication.ExampleUnitTest$testWithTimeoutAndDelay$1$1$1"
)
// withTimeout 代码块
final class ExampleUnitTest$testWithTimeoutAndDelay$1$invokeSuspend$$inlined$runCatching$lambda$1 extends SuspendLambda implements Function2 {
  
   // 状态机
   int label;
   // $FF: synthetic field
   final <undefinedtype> this$0;

   ExampleUnitTest$testWithTimeoutAndDelay$1$invokeSuspend$$inlined$runCatching$lambda$1(Continuation var1, Object var2) {
      super(2, var1);
      this.this$0 = var2;
   }

   @Nullable
   public final Object invokeSuspend(@NotNull Object $result) {
      Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      switch (this.label) {
         case 0:
            // 如果 result 是 Result.Failure ,则抛出 Failure 的异常值
            ResultKt.throwOnFailure($result);
          
            // 迁移到下一个状态,下一次执行走 case 1
            this.label = 1;
            if (DelayKt.delay(30000L, this) == var2) {
               return var2;
            }
            break;
         case 1:
            // 如果 result 是 Result.Failure ,则抛出 Failure 的异常值
            ResultKt.throwOnFailure($result);
            break;
         default:
            throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
      }

      String var4 = "after delay";
      System.out.println(var4);
      ((CountDownLatch)this.this$0.$latch.element).countDown();
      return Unit.INSTANCE;
   }

   @NotNull
   public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
      Intrinsics.checkNotNullParameter(completion, "completion");
      ExampleUnitTest$testWithTimeoutAndDelay$1$invokeSuspend$$inlined$runCatching$lambda$1 var3 = new ExampleUnitTest$testWithTimeoutAndDelay$1$invokeSuspend$$inlined$runCatching$lambda$1(completion, this.this$0);
      return var3;
   }

   public final Object invoke(Object var1, Object var2) {
      return ((ExampleUnitTest$testWithTimeoutAndDelay$1$invokeSuspend$$inlined$runCatching$l
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Kotlin协程是一种轻量级的并发框架,它可以在不创建新线程的情况下实现异步操作。Kotlin协程的实现原理是基于挂起函数和Continuation(协程上下文)。 在Kotlin协程中,挂起函数是指可以被暂停执行,并在稍后继续执行的函数。在挂起函数中,可以使用`suspend`关键字来标记该函数为挂起函数。当调用一个挂起函数时,该函数会返回一个`Continuation`对象,该对象可以被用来在稍后的时间点恢复挂起函数的执行。 Kotlin协程的调度器会在适当的时候调用Continuation对象的`resume`方法来恢复挂起函数的执行。当一个挂起函数被恢复执行时,它会从上一次挂起的地方继续执行,直到函数结束或者再次遇到挂起点。 Kotlin协程的实现原理可以用以下伪代码来说明: ```kotlin fun main() { launch { println("Hello") delay(1000) println("World") } } suspend fun delay(time: Long) { // 挂起当前协程,等待一段时间 // 通过Continuation对象来恢复协程的执行 suspendCoroutine<Unit> { continuation -> Timer().schedule(time) { continuation.resume(Unit) } } } fun launch(block: suspend () -> Unit) { // 创建一个新的协程,并将其加入到调度器中 val coroutine = Coroutine(block) coroutine.start() } class Coroutine(private val block: suspend () -> Unit) { fun start() { block.startCoroutine(this) } } class Continuation(private val coroutine: Coroutine) { fun resume(value: Any?) { coroutine.resume(value) } } suspend fun <T> suspendCoroutine(block: (Continuation<T>) -> Unit): T { // 挂起当前协程,等待Continuation对象被调用 // 通过Continuation对象来恢复协程的执行 return suspendCoroutineOrReturn { continuation -> block(Continuation(coroutine)) } } ``` 在上面的代码中,`launch`函数用于创建一个新的协程,并将其加入到调度器中。`Coroutine`类表示一个协程,`start`方法用于启动协程的执行。`suspendCoroutine`函数用于挂起当前协程,并等待Continuation对象被调用以恢复协程的执行。`delay`函数使用`suspendCoroutine`函数来实现挂起功能,并在一定时间后恢复协程的执行。 Kotlin协程的实现原理比较复杂,但是开发者只需要关注如何使用协程来实现异步操作即可。通过使用协程,开发者可以编写出更加简洁、易于理解、易于维护的异步代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值