Kotlin 协程协程取消 ③ ( finally 释放协程资源 | 使用 use 函数执行 Closeable 对象释放资源操作 | 构造无法取消的协程任务 | 构造超时取消的协程任务 )

Posted 韩曙亮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin 协程协程取消 ③ ( finally 释放协程资源 | 使用 use 函数执行 Closeable 对象释放资源操作 | 构造无法取消的协程任务 | 构造超时取消的协程任务 )相关的知识,希望对你有一定的参考价值。

文章目录





一、释放协程资源



如果 协程中途取消 , 期间需要 释放协程占有的资源 ;

如果执行的协程任务中 , 需要 执行 关闭文件 , 输入输出流 等操作 , 推荐使用 try…catch…finally 代码块 , 在 finally 代码块中的代码 , 即使是协程取消时 , 也会执行 ;


代码示例 :

package kim.hsl.coroutine

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
import kotlin.coroutines.intrinsics.intercepted
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn

class MainActivity : AppCompatActivity()
    val TAG = "MainActivity"

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        runBlocking 
            // 创建协程作用域
            val coroutineScope = CoroutineScope(Dispatchers.Default)

            val job1 = coroutineScope.launch 
                try 
                    Log.i(TAG, "协程任务执行开始")
                    var i = 0
                    while (i < 10000000) 
                        yield()
                        var j = i + 1
                        i++
                        if(j == 10000000) 
                            Log.i(TAG, "最后一次循环 : j = $j")
                            Log.i(TAG, "协程任务执行完毕")
                        
                    
                catch (e: Exception) 
                    Log.i(TAG, "协程抛出异常")
                    e.printStackTrace()
                finally 
                    Log.i(TAG, "释放协程占用的资源")
                
            

            // 100ms 后取消协程作用域
            delay(10)

            Log.i(TAG, "取消协程任务")
            // 取消协程任务
            job1.cancelAndJoin()
            Log.i(TAG, "退出协程作用域")
        
    

执行结果 : 即使是取消协程任务后 , 在协程抛出 JobCancellationException 异常后 , finally 中的代码在最后也被执行了 ;

22:06:06.455  I  协程任务执行开始
22:06:06.504  I  取消协程任务
22:06:06.508  I  协程抛出异常
22:06:06.509  W  kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutineCancelling@bc6a601
22:06:06.509  I  释放协程占用的资源
22:06:06.510  I  退出协程作用域





二、使用 use 函数执行 Closeable 对象释放资源操作



使用 use 函数 可以在 程序结束时 , 执行实现了 Closeable 对象的的 close 方法 , 该操作适合文件对象关闭文件使用 ;


use 函数原型如下 :

/**
 * 在这个资源上执行给定的[block]函数,然后正确关闭它,不管是否异常
 * 是否被抛出。
 *
 * @param block 处理这个[Closeable]资源的函数。
 * @return t在这个资源上调用[block]函数的结果。
 */
@InlineOnly
@RequireKotlin("1.2", versionKind = RequireKotlinVersionKind.COMPILER_VERSION, message = "Requires newer compiler version to be inlined correctly.")
public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R 

代码示例 :

package kim.hsl.coroutine

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.*
import java.io.BufferedReader
import java.io.FileReader

class MainActivity : AppCompatActivity()
    val TAG = "MainActivity"

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        runBlocking 
            // 创建协程作用域
            val coroutineScope = CoroutineScope(Dispatchers.Default)

            val job1 = coroutineScope.launch 
                // Context.getFilesDir() 为 /data/data/com.android.example/files
                BufferedReader(FileReader("$filesDir/name.txt")).use 
                    Log.i(TAG, "协程任务执行开始")
                    delay(20000)
                    Log.i(TAG, "协程任务执行完毕")
                
            

            // 100ms 后取消协程作用域
            delay(10)

            Log.i(TAG, "取消协程任务")
            // 取消协程任务
            job1.cancelAndJoin()
            Log.i(TAG, "退出协程作用域")
        
    


在上述 BufferedReader 的 close 函数中 , 自动关闭了输入流 ;

public class BufferedReader extends Reader 
    public void close() throws IOException 
        synchronized (lock) 
            if (in == null)
                return;
            try 
                in.close();
             finally 
                in = null;
                cb = null;
            
        
    





三、使用 withContext(NonCancellable) 构造无法取消的协程任务



如果在 finally 中需要使用 suspend 挂起函数 , 则 挂起函数以及之后的代码将不会被执行 ;

如下代码 :

val job1 = coroutineScope.launch 
    try 
        Log.i(TAG, "协程任务执行开始")
    finally 
        Log.i(TAG, "释放协程占用的资源")
        delay(1000)
        Log.i(TAG, "释放协程占用的资源完毕")
    

如果在协程取消后 , finally 代码块的代码肯定会执行 , 但是如果 finally 中 delay 挂起函数以及之后的代码将不会被执行 ;


使用 withContext(NonCancellable) 代码块 , 可以构造一个无法取消的协程任务 , 这样可以避免 finally 中的代码无法完全执行 ;

withContext(NonCancellable) 
    Log.i(TAG, "释放协程占用的资源")
    delay(1000)
    Log.i(TAG, "释放协程占用的资源完毕")


代码示例 :

package kim.hsl.coroutine

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.*
import java.io.BufferedReader
import java.io.FileReader

class MainActivity : AppCompatActivity()
    val TAG = "MainActivity"

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        runBlocking 
            // 创建协程作用域
            val coroutineScope = CoroutineScope(Dispatchers.Default)

            val job1 = coroutineScope.launch 
                try 
                    Log.i(TAG, "协程任务执行开始")
                    var i = 0
                    while (i < 10000000) 
                        yield()
                        var j = i + 1
                        i++
                        if(j == 10000000) 
                            Log.i(TAG, "最后一次循环 : j = $j")
                            Log.i(TAG, "协程任务执行完毕")
                        
                    
                catch (e: Exception) 
                    Log.i(TAG, "协程抛出异常")
                    e.printStackTrace()
                finally 
                    withContext(NonCancellable) 
                        Log.i(TAG, "释放协程占用的资源")
                        delay(1000)
                        Log.i(TAG, "释放协程占用的资源完毕")
                    
                
            

            // 100ms 后取消协程作用域
            delay(10)

            Log.i(TAG, "取消协程任务")
            // 取消协程任务
            job1.cancelAndJoin()
            Log.i(TAG, "退出协程作用域")
        
    

执行结果 : finally 代码块中存在挂起函数 , 但是整个代码块被 withContext(NonCancellable) 代码块包裹 , 所有的代码执行完毕 ;

23:12:31.014  I  协程任务执行开始
23:12:31.029  I  取消协程任务
23:12:31.033  I  协程抛出异常
23:12:31.034  W  kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutineCancelling@bc6a601
23:12:31.039  I  释放协程占用的资源
23:12:31.135  I  remove existing package for update: kim.hsl.coroutine
23:12:32.091  I  释放协程占用的资源完毕
23:12:32.093  I  退出协程作用域





四、使用 withTimeoutOrNull 函数构造超时取消的协程任务



使用 withTimeout 函数 , 可以构造超时取消的协程任务 , 在下面的代码中 , 构造的协程任务 , 超时 1000 ms 就会自动取消 , 如果超时则报 kotlinx.coroutines.TimeoutCancellationException 异常信息 ;

代码示例 :

package kim.hsl.coroutine

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout

class MainActivity : AppCompatActivity()
    val TAG = "MainActivity"

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        runBlocking 
            // 构造超时取消的协程任务
            withTimeout(1000) 
                // 超过 1000 ms 没有取消的任务

                Log.i(TAG, "协程任务执行开始")
                delay(12000)
                Log.i(TAG, "协程任务执行结束")
            
        
    

执行结果 :

23:28:08.849  I  协程任务执行开始
23:28:09.888  D  Shutting down VM
23:28:09.894  E  FATAL EXCEPTION: main
                 Process: kim.hsl.coroutine, PID: 8050
                 java.lang.RuntimeException: Unable to start activity ComponentInfokim.hsl.coroutine/kim.hsl.coroutine.MainActivity: kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1000 ms
                 	at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2951)
                 	at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3086)
                 	at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
                 	at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
                 	at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
                 	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1816)
                 	at android.os.Handler.dispatchMessage(Handler.java:106)
                 	at android.os.Looper.loop(Looper.java:193)
                 	at android.app.ActivityThread.main(ActivityThread.java:6718)
                 	at java.lang.reflect.Method.invoke(Native Method)
                 	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
                 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
                 Caused by: kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1000 ms
                 	at kotlinx.coroutines.TimeoutKt.TimeoutCancellationException(Timeout.kt:186)
                 	at kotlinx.coroutines.TimeoutCoroutine.run(Timeout.kt:156)
                 	at kotlinx.coroutines.EventLoopImplBase$DelayedRunnableTask.run(EventLoop.common.kt:507)
                 	at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:277)
                 	at kotlinx.coroutines.DefaultExecutor.run(DefaultExecutor.kt:69)
                 	at java.lang.Thread.run(Thread.java:764)


如果 需要 构造一个超时取消协程 返回一个 返回值 , 则使用 withTimeoutOrNull 函数进行构造 , 如果顺利执行 , 则按照正常返回值返回 , 如果执行超时 , 则直接返回 null ;

代码示例 :

package kim.hsl.coroutine

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeoutOrNull

class MainActivity : AppCompatActivity()
    val TAG = "MainActivity"

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        runBlocking 
            // 构造超时取消的协程任务
            val result = withTimeoutOrNull(1000) 
                // 超过 1000 ms 没有取消的任务

                Log.i(TAG, "协程任务执行开始")
                delay(12000)
                Log.i(TAG, "协程任务执行结束")

                // 执行完毕后的返回值
                // 如果超时则返回 null
                "(withTimeoutOrNull 返回值)"
            

            Log.i(TAG, "上述协程任务的返回值为 $result")
        
    

执行结果 :

23:34:35.778  I  协程任务执行开始
23:34:36.794  I  上述协程任务的返回值为 null

以上是关于Kotlin 协程协程取消 ③ ( finally 释放协程资源 | 使用 use 函数执行 Closeable 对象释放资源操作 | 构造无法取消的协程任务 | 构造超时取消的协程任务 )的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin 协程协程底层实现 ③ ( 结构化并发 | MainScope 作用域 | 取消协程作用域 | Activity 实现 CoroutineScope 协程作用域接口 )

Kotlin 协程协程底层实现 ③ ( 结构化并发 | MainScope 作用域 | 取消协程作用域 | Activity 实现 CoroutineScope 协程作用域接口 )

Kotlin 协程协程异常处理 ③ ( 协程异常处理器 CoroutineExceptionHandler 捕获异常 | 验证 CoroutineScope 协程的异常捕捉示例 )

Kotlin 协程协程异常处理 ③ ( 协程异常处理器 CoroutineExceptionHandler 捕获异常 | 验证 CoroutineScope 协程的异常捕捉示例 )

Kotlin 协程协程启动 ③ ( 协程组合并发 | 挂起函数串行执行 | 协程组合并发执行挂起函数 )

Kotlin 协程协程启动 ③ ( 协程组合并发 | 挂起函数串行执行 | 协程组合并发执行挂起函数 )