尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Kotlin中级——协程

Kotlin中级——协程 协程协程可以暂停执行而不是阻塞线程。这允许一个协程在等待某些数据到达时挂起另一个协例程在同一线程上运行从而确保有效的资源利用率。依赖dependencies { implementation org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0 }协程创建创建协程需要suspend 方法协程作用域如withContext()协程构造器如CoroutineScope.launch()调度器用于控制协程使用的线程如Dispatchers.Default如下相当于挂起切换到线程池运行里面的协程代码等全部运行完之后再切换到原来的环境期间主线程完全空闲可以做别的任何事classCoroutinesBasicsTest{suspendfungreet(){println(The greet() on the thread:${Thread.currentThread().name})// Suspends for 1 second and releases the threaddelay(1.seconds)// The delay() function simulates a suspending API call here// You can add suspending API calls here like a network request}suspendfunmain(){// Runs the code inside this block on a shared thread poolwithContext(Dispatchers.Default){// this: CoroutineScopethis.launch(){greet()}// Starts another coroutinethis.launch(){println(The CoroutineScope.launch() on the thread:${Thread.currentThread().name})delay(1.seconds)// The delay function simulates a suspending API call here// You can add suspending API calls here like a network request}println(The withContext() on the thread:${Thread.currentThread().name})}}TestfunrunCoroutinesExample()runBlocking{println(——————————————————————————————————————————————————)main()println(——————————————————————————————————————————————————)}}打印类似如下每次输出顺序和线程名称都不同取决于闲置的线程The greet() on the thread: DefaultDispatcher-worker-2 coroutine#2 The withContext() on the thread: DefaultDispatcher-worker-1 coroutine#1 The CoroutineScope.launch() on the thread: DefaultDispatcher-worker-3 coroutine#3 The greet() on the thread: DefaultDispatcher-worker-2 coroutine#2 The CoroutineScope.launch() on the thread: DefaultDispatcher-worker-3 coroutine#3 The withContext() on the thread: DefaultDispatcher-worker-1 coroutine#1Suspend允许正在运行的操作在不影响代码结构的情况下暂停和稍后恢复只能从另一个Suspend函数调用Suspend函数协程作用域父协程在完成之前等待其子协程完成。如果父协程失败或被取消则其所有子协程也会被递归取消新的协程只能在定义和管理其生命周期的CoroutionScope中启动当你在另一个协程中启动一个例程时它会自动成为其父作用域的子作用域。如CoroutinScope.launch()启动的任何协程都会成为它的子协程coroutineScope如下coroutineScope()开个新作用域继承上下文调度器未指定则为Dispatchers.Default等里面所有协程全跑完才返回coroutineScope() 继承的调度器等于Dispatchers.Default时相当于withContext(Dispatchers.Default)withContext多了一个调度和环境切换功能classCoroutinesBasicsTest{suspendfunmain(){// Root of the coroutine subtreecoroutineScope{// this: CoroutineScopethis.launch{this.launch{delay(2.seconds)println(Child of the enclosing coroutine completed)}println(Child coroutine 1 completed)}this.launch{delay(1.seconds)println(Child coroutine 2 completed)}}// Runs only after all children in the coroutineScope have completedprintln(Coroutine scope completed)}TestfunrunCoroutinesExample()runBlocking{println(——————————————————————————————————————————————————)main()println(——————————————————————————————————————————————————)}}协程构建器CoroutineScope.launch在现有协程作用域内启动一个新的协程而不会阻塞作用域的其余部分。返回一个Job句柄使用此句柄等待启动的协程完成class CoroutinesBasicsTest { suspend fun performBackgroundWork() coroutineScope { // this: CoroutineScope // Starts a coroutine that runs without blocking the scope this.launch { // Suspends to simulate background work delay(100.milliseconds) println(Sending notification in background) } // Main coroutine continues while a previous one suspends println(Scope continues) } Test fun runCoroutinesExample() runBlocking { println(——————————————————————————————————————————————————) performBackgroundWork() println(——————————————————————————————————————————————————) } }Scope continues Sending notification in backgroundCoroutineScope.async在现有协程范围内启动并发计算并返回一个表示最终结果的Deferred句柄。使用await()函数挂起代码直到结果就绪class CoroutinesBasicsTest { suspend fun main() withContext(Dispatchers.Default) { // this: CoroutineScope // Starts downloading the first page val firstPage this.async { delay(50.milliseconds) First page } // Starts downloading the second page in parallel val secondPage this.async { delay(100.milliseconds) Second page } // Awaits both results and compares them val pagesAreEqual firstPage.await() secondPage.await() println(Pages are equal: $pagesAreEqual) } Test fun runCoroutinesExample() runBlocking { println(——————————————————————————————————————————————————) main() println(——————————————————————————————————————————————————) } }Pages are equal: falserunBlocking创建一个协程作用域并阻塞当前线程直到在该作用域中启动的协程完成仅当没有其他选项从非挂起代码调用挂起代码时才使用runBlocking如java调用kt代码suspend需要同步返回// A third-party interface you cant change interface Repository { fun readItem(): Int } object MyRepository : Repository { override fun readItem(): Int { // Bridges to a suspending function return runBlocking { myReadItem() } } } suspend fun myReadItem(): Int { delay(100.milliseconds) return 4 }协程调度器控制哪个线程或线程池协程用于执行协程并不总是与单个线程相关联。他们可以在一个线程上暂停在另一个线程中继续默认情况下协程从其父作用域继承调度器如果协程上下文不包括调度器默认Dispatchers.Defaultsuspend fun runWithDispatcher() coroutineScope { // this: CoroutineScope this.launch(Dispatchers.Default) { println(Running on ${Thread.currentThread().name}) } }取消和超时如下代码变量childStarted确保协程先启动再取消awaitCancellation()让协程挂起直到它被取消相当于delay(Duration.INFINITE)launch返回job句柄调用cancel()awaitCancellation()在下次检查取消时抛出CancellationException捕获异常后一定要再次抛出class CoroutinesBasicsTest { Test fun runCoroutinesExample() runBlocking { println(——————————————————————————————————————————————————) withContext(Dispatchers.Default) { // Used as a signal that the coroutine has started running val childStarted CompletableDeferredUnit() val childJob: Job launch { println(The coroutine has started) // Completes the CompletableDeferred, // signaling that the coroutine has started running childStarted.complete(Unit) try { // Suspends indefinitely // This call will never return unless the coroutine is canceled awaitCancellation() } catch (e: CancellationException) { println(The coroutine was canceled: $e) // Always rethrow cancellation exceptions! throw e } println(This line will never be executed) } // Waits for the coroutine to start before canceling it childStarted.await() // Cancels the coroutine, // so awaitCancellation() throws a CancellationException childJob.cancel() } // Coroutine builders such as withContext() or coroutineScope() // wait for all child coroutines to complete, // even when the children are canceled println(All coroutines have completed) println(——————————————————————————————————————————————————) } }取消传递取消协程也会取消其所有子协程这里CompletableDeferred只保证启动协程并不保证运行协程协程可能在实际运行前被取消class CoroutinesBasicsTest { Test fun runCoroutinesExample() runBlocking { println(——————————————————————————————————————————————————) // Used as a signal that the child coroutines have been launched val childrenLaunched CompletableDeferredUnit() // Launches two child coroutines val parentJob launch { launch { println(Child coroutine 1 has started running) try { awaitCancellation() } finally { println(Child coroutine 1 has been canceled) } } launch { println(Child coroutine 2 has started running) try { awaitCancellation() } finally { println(Child coroutine 2 has been canceled) } } // Completes the CompletableDeferred, // signaling that the child coroutines have been launched childrenLaunched.complete(Unit) } // Waits for the parent coroutine to signal that it has launched // all of its children childrenLaunched.await() // Cancels the parent coroutine, which cancels all its children parentJob.cancel() println(——————————————————————————————————————————————————) } }Child coroutine 1 has started running Child coroutine 2 has started running Child coroutine 1 has been canceled Child coroutine 2 has been canceled取消的作用挂起点当协程被取消时它会继续运行直到它到达代码中可能挂起的点也称为挂起点挂起函数内部使用suspendCancellableCorotine()检查它是否已被取消。如果有协程将停止并抛出CancellationException如下是常见的挂起函数单纯只是想演示挂起直到取消 → awaitCancellation()等超时/等一段时间 → delay()等另一个协程发数据过来 → channel.receive()等另一个协程算出一个结果 → deferred.await()等锁被释放(并发控制) → mutex.lock()class CoroutinesBasicsTest { Test fun runCoroutinesExample() runBlocking { println(——————————————————————————————————————————————————) withContext(Dispatchers.Default) { val childJobs listOf( launch { // Suspends until canceled awaitCancellation() }, launch { // Suspends until canceled delay(Duration.INFINITE) }, launch { val channel ChannelInt() // Suspends while waiting for a value thats never sent channel.receive() }, launch { val deferred CompletableDeferredInt() // Suspends while waiting for a value thats never completed deferred.await() }, launch { val mutex Mutex(locked true) // Suspends while waiting for a mutex that remains locked indefinitely mutex.lock() } ) // Gives the child coroutines time to start and suspend delay(100.milliseconds) // Cancels all child coroutines childJobs.forEach { it.cancel() } } println(All child jobs completed!) println(——————————————————————————————————————————————————) } }yield()协程在线程中顺序执行若一个协程没有挂起它无法响应取消且其他协程无法在相同线程中执行长时间运行而没有挂起的代码中定期调用yield()函数为其他协程提供运行计划并定期检查取消情况class CoroutinesBasicsTest { Test fun runCoroutinesExample() runBlocking { println(——————————————————————————————————————————————————) runBlocking { val coroutineCount 5 repeat(coroutineCount) { coroutineIndex - launch { val id coroutineIndex 1 repeat(5) { iterationIndex - val iteration iterationIndex 1 // Suspends temporarily to give other coroutines a chance to run // Without this, the coroutines run sequentially yield() // Prints the coroutine index and iteration index println($id * $iteration ${id * iteration}) } } } } println(——————————————————————————————————————————————————) } }1 * 1 1 2 * 1 2 3 * 1 3 4 * 1 4 5 * 1 5 1 * 2 2 2 * 2 4 3 * 2 6 4 * 2 8 5 * 2 10 ......主动检查是否被取消取消协程时isActive属性返回false当协程被取消时ensureActive函数会抛出CancellationException利用协程取消中断线程要在取消协程时中断线程请将阻塞代码包装在runInterruptible()class CoroutinesBasicsTest { Test fun runCoroutinesExample() runBlocking { println(——————————————————————————————————————————————————) withContext(Dispatchers.Default) { val childStarted CompletableDeferredUnit() val childJob launch { try { // Cancellation triggers a thread interruption runInterruptible { childStarted.complete(Unit) try { // Blocks the current thread for a very long time Thread.sleep(Long.MAX_VALUE) } catch (e: InterruptedException) { println(Thread interrupted (Java): $e) throw e } } } catch (e: CancellationException) { println(Coroutine canceled (Kotlin): $e) throw e } } childStarted.await() // Cancels the coroutine and interrupts the thread executing Thread.sleep() childJob.cancel() } println(——————————————————————————————————————————————————) } }Thread interrupted (Java): java.lang.InterruptedException: sleep interrupted Coroutine canceled (Kotlin): kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; jobcoroutine#2:StandaloneCoroutine{Cancelling}7afec4b2取消时处理资源协程执行到下一个挂起点时检测到取消会立即抛出 CancellationException不再继续执行后面的代码块相关资源应该利用try-finally释放class CoroutinesBasicsTest { // 模拟一个数据库连接实现 AutoCloseable方便统一管理 class FakeDatabaseConnection(private val id: Int) : AutoCloseable { init { println([DB-$id] 连接已打开) } suspend fun query(userId: String): String { println([DB-$id] 开始查询用户 $userId ...) delay(1.seconds) // 模拟查询耗时这是一个挂起点 println([DB-$id] 查询完成) return User($userId) } override fun close() { println([DB-$id] 连接已关闭) } } fun openDatabaseConnection(id: Int): FakeDatabaseConnection FakeDatabaseConnection(id) // ———————————— 有坑的版本 ———————————— suspend fun loadUserProfileBad(scope: CoroutineScope, userId: String): Job { return scope.launch { val db withContext(Dispatchers.IO) { openDatabaseConnection(1) } // 查询过程中如果被取消下面 db.close() 永远不会执行 val user db.query(userId) println(Bad 版本更新 UI$user) db.close() } } // ———————————— 正确的版本用 finally 保证一定会关闭 ———————————— suspend fun loadUserProfileGood(scope: CoroutineScope, userId: String): Job { return scope.launch { var db: FakeDatabaseConnection? null try { db withContext(Dispatchers.IO) { openDatabaseConnection(2) } val user db.query(userId) println(Good 版本更新 UI$user) } finally { // 不管协程是正常走完还是中途被取消这里都会执行 db?.close() } } } suspend fun main() { withContext(Dispatchers.Default) { println( 演示有坑的版本 ) val badJob loadUserProfileBad(this, u001) delay(300.milliseconds) // 让协程先跑起来db 已经打开但查询还没完成 badJob.cancel() // 取消db.close() 永远不会跑到 badJob.join() println() println( 演示正确的版本 ) val goodJob loadUserProfileGood(this, u002) delay(300.milliseconds) goodJob.cancel() // 取消finally 里的 db.close() 依然会执行 goodJob.join() } } Test fun runCoroutinesExample() runBlocking { println(——————————————————————————————————————————————————) main() println(——————————————————————————————————————————————————) } }不可取消代码块协程一旦被取消即使在 finally 块里只要还调用了挂起函数依然会被取消打断需要确保某些操作完成时例如使用suspend的close()函数关闭资源可以使用withContext(NonCancellable){}class CoroutinesBasicsTest { val serviceStarted CompletableDeferredUnit() fun startService() { println(Starting the service...) serviceStarted.complete(Unit) } suspend fun shutdownServiceAndWait() { println(Shutting down...) delay(100.milliseconds) println(Successfully shut down!) } suspend fun main() { withContext(Dispatchers.Default) { val childJob launch { startService() try { awaitCancellation() } finally { withContext(NonCancellable) { // Without withContext(NonCancellable), // this function doesnt complete because the coroutine is canceled shutdownServiceAndWait() } } } serviceStarted.await() childJob.cancel() } println(Exiting the program) } Test fun runCoroutinesExample() runBlocking { println(——————————————————————————————————————————————————) main() println(——————————————————————————————————————————————————) } }超时超时允许您在指定的持续时间后自动取消协程要指定超时请使用withTimeoutOrNull()超时返回nullclass CoroutinesBasicsTest { suspend fun slowOperation(): String { try { delay(300.milliseconds) return A } catch (e: CancellationException) { println(The slow operation has been canceled: $e) throw e } } suspend fun fastOperation(): String { try { delay(15.milliseconds) return B } catch (e: CancellationException) { println(The fast operation has been canceled: $e) throw e } } suspend fun main() { withContext(Dispatchers.Default) { val slow withTimeoutOrNull(100.milliseconds) { slowOperation() } println(The slow operation finished with $slow) val fast withTimeoutOrNull(100.milliseconds) { fastOperation() } println(The fast operation finished with $fast) } } Test fun runCoroutinesExample() runBlocking { println(——————————————————————————————————————————————————) main() println(——————————————————————————————————————————————————) } }The slow operation has been canceled: kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 100 ms The slow operation finished with null The fast operation finished with B
返回列表