概述
接下来我会围绕一个问题探讨一下 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
抛出的 TimeoutCancellationException
是 CancellationException
的子类,由于取消只是一个异常,所有的资源都可以按照通常的方式关闭。如果需要在任何类型的超时发生时执行某些额外的操作,则可以将代码包装在 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