zl程序教程

您现在的位置是:首页 >  后端

当前栏目

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

Kotlin资源对象执行 使用 操作 函数 无法
2023-06-13 09:18:06 时间

文章目录

一、释放协程资源


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

如果执行的协程任务中 , 需要 执行 关闭文件 , 输入输出流 等操作 , 推荐使用 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=StandaloneCoroutine{Cancelling}@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=StandaloneCoroutine{Cancelling}@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 ComponentInfo{kim.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