学习协程2: 取消和超时

您所在的位置:网站首页 gkui远程控制超时 学习协程2: 取消和超时

学习协程2: 取消和超时

2023-05-18 05:59| 来源: 网络整理| 查看: 265

Kotlin协程官方网址

Cancelling coroutine execution

对于长时间运行的程序,需要进行粒度控制,在合适的时间结束协程。launch返回一个job对象,可以使用该对象取消正在运行的协程。取消是抛出 CancellationException,如果不捕捉,协程被取消。

fun cancelCoroutine() = runBlocking { val job = launch { repeat(1000) { i -> println("job: I'm sleeping $i ...") delay(500L) } } delay(1300L) // delay a bit println("main: I'm tired of waiting!") job.cancel() // cancels the job job.join() // waits for job's completion println("main: Now I can quit.") } // output job: I'm sleeping 0 ... job: I'm sleeping 1 ... job: I'm sleeping 2 ... main: I'm tired of waiting! main: Now I can quit.

开启一个协程开始打印,没打印一次延时500ms,整个线程延时1300ms, 因此打印三次。然后取消协程,等待协程结束后打印最后的语句。

Cancellation is cooperative

取消是协作的,一个协程可以被取消必须是与其他协作。kotlinx.coroutines中的挂起函数都是可取消的。但是,如果协程在计算中工作并且不检查取消,则无法取消。如下:

val startTime = System.currentTimeMillis() val job = launch(Dispatchers.Default) { var nextPrintTime = startTime var i = 0 while (i println("job: I'm sleeping ${i++} ...") nextPrintTime += 500L } } } delay(1300L) // delay a bit println("main: I'm tired of waiting!") job.cancelAndJoin() // cancels the job and waits for its completion println("main: Now I can quit.") //output job: I'm sleeping 0 ... job: I'm sleeping 1 ... job: I'm sleeping 2 ... main: I'm tired of waiting! job: I'm sleeping 3 ... job: I'm sleeping 4 ... main: Now I can quit.

同样的问题可以观察到通过捕捉 CancellationException

val job = launch(Dispatchers.Default) { repeat(5) { i -> try { // print a message twice a second println("job: I'm sleeping $i ...") delay(500) } catch (e: Exception) { // log the exception println(e) } } } delay(1300L) // delay a bit println("main: I'm tired of waiting!") job.cancelAndJoin() // cancels the job and waits for its completion println("main: Now I can quit.") // output job: I'm sleeping 0 ... job: I'm sleeping 1 ... job: I'm sleeping 2 ... main: I'm tired of waiting! kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@23ff831c job: I'm sleeping 3 ... kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@23ff831c job: I'm sleeping 4 ... kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@23ff831c main: Now I can quit.

在取消协程是会抛出 CancellationException,但是,如果协程在计算中工作并且不检查取消,则无法取消。

Making computation code cancellable

两种方法可以取消计算工作的协程。

第一种方法是定期调用检查取消的挂起函数。有一个yield函数是一个很好的选择。另一个是显式检查取消状态。 use yield

如果要处理的任务属于 1) CPU 密集型,2) 可能会耗尽线程池资源,3) 需要在不向线程池中添加更多线程的前提下允许线程处理其他任务,那么请使用 yield()。如果 job 已经完成,由 yield 所处理的首要任务将会是检查任务的完成状态,完成的话则直接通过抛出 CancellationException 来退出协程。yield 可以作为定期检查所调用的第一个函数

val startTime = System.currentTimeMillis() val job = launch(Dispatchers.Default) { var nextPrintTime = startTime var i = 0 while (i println("job: I'm sleeping ${i++} ...") nextPrintTime += 500L } } } delay(1300L) // delay a bit println("main: I'm tired of waiting!") job.cancelAndJoin() // cancels the job and waits for its completion println("main: Now I can quit.") // out put job: I'm sleeping 0 ... job: I'm sleeping 1 ... job: I'm sleeping 2 ... main: I'm tired of waiting! main: Now I can quit. explicitly check the cancellation status val startTime = System.currentTimeMillis() val job = launch(Dispatchers.Default) { var nextPrintTime = startTime var i = 0 while (i println("job: I'm sleeping ${i++} ...") nextPrintTime += 500L } } } delay(1300L) // delay a bit println("main: I'm tired of waiting!") job.cancelAndJoin() // cancels the job and waits for its completion println("main: Now I can quit.") //output job: I'm sleeping 0 ... job: I'm sleeping 1 ... job: I'm sleeping 2 ... main: I'm tired of waiting! main: Now I can quit. Closing resources with finally

可取消挂起函数在取消时抛出CancellationException,这可以用通常的方式处理。例如,try{…} finally{…}表达式和Kotlin的use函数在协程被取消时正常执行它们的结束动作。

val job = launch { try { repeat(1000) { i -> println("job: I'm sleeping $i ...") delay(500L) } } finally { println("job: I'm running finally") } } delay(1300L) // delay a bit println("main: I'm tired of waiting!") job.cancelAndJoin() // cancels the job and waits for its completion println("main: Now I can quit.") //output job: I'm sleeping 0 ... job: I'm sleeping 1 ... job: I'm sleeping 2 ... main: I'm tired of waiting! job: I'm running finally main: Now I can quit. Run non-cancellable block

在finally中使用任何挂起函数都会造成 CancellationException, 因为协程已经被取消了。如果需要在取消的协程中挂起,使用withContext(NonCancellable) {...}使用 withContext 函数和NonCancellable context 。

val job = launch { try { repeat(1000) { i -> println("job: I'm sleeping $i ...") delay(500L) } } finally { withContext(NonCancellable) { println("job: I'm running finally") delay(1000L) println("job: And I've just delayed for 1 sec because I'm non-cancellable") } } } delay(1300L) // delay a bit println("main: I'm tired of waiting!") job.cancelAndJoin() // cancels the job and waits for its completion println("main: Now I can quit.") // output job: I'm sleeping 0 ... job: I'm sleeping 1 ... job: I'm sleeping 2 ... main: I'm tired of waiting! job: I'm running finally job: And I've just delayed for 1 sec because I'm non-cancellable main: Now I can quit. Timeout

使用 withTimeout在超时后取消协程。

withTimeout(1300L) { repeat(1000) { i -> println("I'm sleeping $i ...") delay(500L) } } //output 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的子类。以前没有在控制台上看到它的堆栈跟踪。这是因为在取消的协程中,CancellationException被认为是协程完成的正常原因。然而,在这个例子中,在main函数中使用了withTimeout。由于取消只是一个异常,所以所有资源都以通常的方式关闭。可以在try{…} catch (e: TimeoutCancellationException){…}块,如果需要在任何类型的超时时执行一些额外的操作,或者使用与withTimeout类似的withTimeoutOrNull函数,但它在超时时返回null,而不是抛出异常。

try { withTimeout(1300L) { repeat(1000) { i -> println("I'm sleeping $i ...") delay(500L) } } } catch (e:TimeoutCancellationException) { println(e.message) } println("this is end") // output I'm sleeping 0 ... I'm sleeping 1 ... I'm sleeping 2 ... Timed out waiting for 1300 ms this is end 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") // output I'm sleeping 0 ... I'm sleeping 1 ... I'm sleeping 2 ... Result is null Asynchronous timeout and resources

withTimeout中的超时事件相对于在其块中运行的代码是异步的,并且可以在任何时间发生,甚至在从超时块内部返回之前发生。如果在块内部打开或获取一些需要在块外部关闭或释放的资源,请记住这一点。例如,这里我们用resource类模拟一个可关闭的资源,它只是通过增加获取的计数器和减少其close函数中的计数器来跟踪它被创建的次数。现在让我们创建许多协程,每个协程在withTimeout块的末尾创建一个Resource,并在块外部释放资源。我们添加了一个小延迟,以便在withTimeout块已经完成时更有可能发生超时,这将导致资源泄漏。

var acquired = 0 class Resource { init { acquired++ } // Acquire the resource fun close() { acquired-- } // Release the resource } fun main() { runBlocking { repeat(10_000) { // Launch 10K coroutines launch { val resource = withTimeout(60) { // Timeout of 60 ms delay(50) // Delay for 50 ms Resource() // Acquire a resource and return it from withTimeout block } resource.close() // Release the resource } } } // Outside of runBlocking all coroutines have completed println(acquired) // Print the number of resources still acquired } //output 1103

要解决这个问题,可以将对资源的引用存储在变量中,而不是从withTimeout块返回它。

runBlocking { repeat(10_000) { // Launch 10K coroutines launch { var resource: Resource? = null // Not acquired yet try { withTimeout(60) { // Timeout of 60 ms delay(50) // Delay for 50 ms resource = Resource() // Store a resource to the variable if acquired } // We can do something else with the resource here } finally { resource?.close() // Release the resource if it was acquired } } } } // Outside of runBlocking all coroutines have completed println(acquired) // Print the number of resources still acquired //output 0


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3