参考链接
示例来自bilibili Kotlin语言深入解析 张龙老师的视频
1 父协程总是等待子协程执行完成
**
* 父协程总是等待子协程执行完成
* 对于父协程来说,父协程总是会等待所有子协程完成,而不必显示地追踪由它启动的子协程,子协程也不需要调用自身的Job.join方法来让父协程等待子协程完成
*/
fun main() = runBlocking {
val job = launch {
repeat(5) { i ->
launch {
delay(i * 200L)
println("Coroutine $i 执行完毕")
}
}
println("hello")
}
/**
* join方法Suspends the coroutine until this job is complete
* 这里调用这个方法主要目的是让”Coroutine x 执行完毕“在”end“之前输出
* 事实上 这里注释调join方法 子协程仍然会能够执行结束
*/
job.join()
println("end")
}
class HelloKotlin7 {
}
2 给协程取名字
import kotlinx.coroutines.*
/**
* 给协程取名字
*
* CoroutineName上下文元素可以让我们对协程取名字,以便能够输出可读性较好的日志
*
* 注意加上-Dkotlinx.coroutines.debug
*/
private fun log(logMessage: String) = println("[${Thread.currentThread().name}] $logMessage")
/**
* CoroutineName看其定义:public data class CoroutineName
* CoroutineName是一个数据类 继承了AbstractCoroutineContextElement AbstractCoroutineContextElement又实现了接口 Element
* 接口Element则实现了CoroutineContext接口
* 因此CoroutineName也是一个CoroutineCoroutineContext
*
* CoroutineName可以用来给用户指定协程的名称 该名称可以再debug模式使用
* User-specified name of coroutine. This name is used in debugging mode.
* See [newCoroutineContext][CoroutineScope.newCoroutineContext] for the description of coroutine debugging facilities.
*/
fun main() = runBlocking(CoroutineName("myCoroutineName1")) {
log("hello")
val value1 = async(CoroutineName("myCoroutineName2")) {
delay(800)
log("myCoroutine2 log")
30
}
val value2 = async(CoroutineName("myCoroutineName3")) {
delay(1000)
log("myCoroutine3 log")
5
}
log("result is ${value1.await() + value2.await()}")// 可以通过在一个deferred上调用await方法来获取最终计算的结果值
}
/**
输出
[main @myCoroutineName1#1] hello
[main @myCoroutineName2#2] myCoroutine2 log
[main @myCoroutineName3#3] myCoroutine3 log
[main @myCoroutineName1#1] result is 35
*/
class HelloKotlin8 {
}
3 协程操作符重载
import kotlinx.coroutines.*
/**
* 既想给协程构建器指定协程名称 又想指定协程分发器
* 操作符重载
* 注意加上-Dkotlinx.coroutines.debug
*/
private fun log(logMessage: String) = println("[${Thread.currentThread().name}] $logMessage")
/**
* 追踪+号 我们发现运算符重载
* Returns a context containing elements from this context and elements from other [context].
* The elements from this context with the same key as in the other one are dropped.
* ”+“返回了一个一个上下文 该上下文包含了 当前context的元素以及其他context的元素
* 如果当前context的元素和其他context的元素拥有相同的key 则会被抛弃
*/
fun main() = runBlocking<Unit> {
launch(CoroutineName("myCoroutineName") + Dispatchers.Default) {
log("myCoroutine log")
}
}
/**
输出
[DefaultDispatcher-worker-1 @myCoroutineName#2] myCoroutine log
*/
class HelloKotlin9 {
}
4 让普通类也具有协程特点
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.Default
/**
* 让普通类也具有协程特点
* 将Activity变成协程 这样 在他里面创建的协程都会变成子协程
* 模拟Android中Activity关闭 所有子协程都关闭
* 达到协程的生命周期与Activity的生命周期绑定的效果
* 写法是让Activity继承CoroutineScope 并在Activity销毁时取消协程 这样 其子协程也会一并取消
*
* 在Android开发中 如果Activity退出 而依附于Activity的协程却没有取消 会导致内存泄漏
* 这里我们模拟创建Activity及其销毁
*/
class Activity:CoroutineScope by CoroutineScope(Dispatchers.Default) {
fun destroy(){
cancel()//调用代理类的方法
}
fun doSthCostTime(){
repeat(8){ i->
launch {
delay(i*300L)
println("coroutine $i is finished")
}
}
}
}
fun main() = runBlocking {
val activity = Activity()
println("start coroutine")
activity.doSthCostTime()
delay(1300L)
println("mock activity destroyed")
activity.destroy()
delay(3000L)
println("end")
}
/**
* CoroutineScope内部实现
* 官方对于CoroutineScope方法的定义和解释
* public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
* ContextScope(if (context[Job] != null) context else context + Job())
*
* Creates a [CoroutineScope] that wraps the given coroutine [context].
* 创建一个包含给定协程上下文的CoroutineScope
*
* If the given [context] does not contain a [Job] element, then a default `Job()` is created.
* This way, cancellation or failure of any child coroutine in this scope cancels all the other children,
* just like inside [coroutineScope] block.
* 如果给定的context没有Job元素,那么会创建一个默认的Job,这样,在该范围的任何子协程被取消或出现异常会取消其他子协程,就像其他子协程也在该作用域中
*
*
*
*
*
*
* CoroutineScope另外一种实现 适用于Android UI线程 即MainScope
* 该方法专门针对图形界面设计
* Creates the main [CoroutineScope] for UI components.
*
* Example of use:
* ```
* class MyAndroidActivity {
* private val scope = MainScope()
*
* override fun onDestroy() {
* super.onDestroy()
* scope.cancel()
* }
* }
* ```
*
* The resulting scope has [SupervisorJob] and [Dispatchers.Main] context elements.
* If you want to append additional elements to the main scope, use [CoroutineScope.plus] operator:
* `val scope = MainScope() + CoroutineName("MyActivity")`.
* public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
* 该CoroutineScope包含了SupervisorJob以及Dispatchers.Main上下文元素
*
*
*
* 关于Dispatchers.Main
* A coroutine dispatcher that is confined to the Main thread operating with UI objects.
* This dispatcher can be used either directly or via [MainScope] factory.
* Usually such dispatcher is single-threaded.
* Dispatchers.Main是一个绑定到操作UI对象的主线程的协程分发器
* 该分发器可以直接使用或者通过MainScope工厂使用
* 通常 该分发器是单线程的
*
* Access to this property may throw [IllegalStateException] if no main thread dispatchers are present in the classpath.
*
* Depending on platform and classpath it can be mapped to different dispatchers:
* - On JS and Native it is equivalent of [Default] dispatcher.
* - On JVM it is either Android main thread dispatcher, JavaFx or Swing EDT dispatcher. It is chosen by
* [`ServiceLoader`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html).
*
* In order to work with `Main` dispatcher, the following artifacts should be added to project runtime dependencies:
* - `kotlinx-coroutines-android` for Android Main thread dispatcher
* - `kotlinx-coroutines-javafx` for JavaFx Application thread dispatcher
* - `kotlinx-coroutines-swing` for Swing EDT dispatcher
*
* In order to set a custom `Main` dispatcher for testing purposes, add the `kotlinx-coroutines-test` artifact to
* project test dependencies.
*
* Implementation note: [MainCoroutineDispatcher.immediate] is not supported on Native and JS platforms.
*
* public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
*
*
* CoroutineScope是什么
* Defines a scope for new coroutines. Every **coroutine builder** (like [launch], [async], etc)
* is an extension on [CoroutineScope] and inherits its [coroutineContext][CoroutineScope.coroutineContext]
* to automatically propagate all its elements and cancellation.
* CoroutineScope 定义了一个新 Coroutine 的执行 Scope。每个 coroutine builder 都是 CoroutineScope 的扩展函数,
* 并且自动的继承了当前 Scope 的 coroutineContext 和取消操作
*/
class HelloKotlin10 {
}
上面只是模拟Android中的Activity 如果实际在Activity中使用 应该如下
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import kotlinx.coroutines.*
// 使用MainScope需要添加依赖kotlinx-coroutines-android以及kotlinx-coroutines-core
// 否则报错 Module with the Main dispatcher is missing. Add dependency providing the Main dispatcher,
// e.g. 'kotlinx-coroutines-android' and ensure it has the same version as 'kotlinx-coroutines-core'
class MainActivity2 : AppCompatActivity(), CoroutineScope by MainScope() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
doSthCostTime()
}
override fun onDestroy() {
super.onDestroy()
cancel()
}
fun close(view: View?) {
this.finish()
}
private fun doSthCostTime() {
repeat(100) { i ->
launch {
delay(i * 300L)
println("coroutine $i is finished")
}
}
}
fun to3(view: View?) {
val intent = Intent(this, MainActivity3::class.java)
startActivity(intent)
}
}
可以自行搜索MainScope在Android中的应用 不过注意添加额外的gradle依赖
apply plugin: 'kotlin-android'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
完整gradle如下
plugins {
id 'com.android.application'
}
apply plugin: 'kotlin-android'
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.example.startkotlin"
minSdkVersion 24
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
}
5 协程线程与ThreadLocal
这里其实主要讲的是ThreadLocal<T>的扩展方法asContextElement
但是我感觉自己还有不少问题 没明白它的真正用途 网上的文章基本copy自Kotlin官网 权且整理我的学习笔记 看看以后能不能解决现在还看不懂的问题吧
import kotlinx.coroutines.*
/**
* 协程 线程与ThreadLocal
*
* 协程不会绑定到特定的线程(比如yield协程后 协程下次继续执行可能会切换到另外的线程
* 又比如HelloKotlin4中可以切换协程上下文,切换上下文的同时可能就切换了线程)
* ThreadLocal却是和线程绑定的
*
* 本节的目的是协程在A线程运行时 可以得到线程A中保存的数据
* 如果协程切换到B线程运行 又可以得到线程B中保存的数据
* 在A B 线程之间来回切换 不会导致数据丢失 错乱
*
* 关于ThreadLocal可以参考我之前的文章
* https://blog.csdn.net/u011109881/article/details/119334195
* 需要注意其set get方法 以及其中的Map对象的Key 与 Value
*
* 最终会发现本案例没有什么特殊的 就是不同的线程中的ThreadLocal里面保存的值不同
*
* 可以加上配置 -Dkotlinx.coroutines.debug
*/
private val threadLocal = ThreadLocal<String>()
private fun log(logMessage: String) = println("[${Thread.currentThread().name}] $logMessage")
fun main() = runBlocking {
threadLocal.set("main")
log("current thread local value ${threadLocal.get()}")
val job = launch (Dispatchers.Default+ threadLocal.asContextElement(value = "launch")){
log("launch current thread local value ${threadLocal.get()}")
yield()//将当前方法执行者让出来 让其他线程运行
log("after yield current thread local value ${threadLocal.get()}")
}
job.join()
log("last current thread local value ${threadLocal.get()}")
}
/**
* asContextElement的部分官方文档
* 定义:
* public fun <T> ThreadLocal<T>.asContextElement(value: T = get()): ThreadContextElement<T> = ThreadLocalElement(value, this)
*
* Wraps [ThreadLocal] into [ThreadContextElement]. The resulting [ThreadContextElement]
* maintains the given [value] of the given [ThreadLocal] for coroutine regardless of the actual thread its is resumed on.
* By default [ThreadLocal.get] is used as a value for the thread-local variable, but it can be overridden with [value] parameter.
* Beware that context element **does not track** modifications of the thread-local and accessing thread-local from coroutine
* without the corresponding context element returns **undefined** value. See the examples for a detailed description.
*
* asContextElement是ThreadLocal<T>的扩展方法 它将ThreadLocal封装为ThreadContextElement,ThreadContextElement为协程中给定的ThreadLocal维护给定
* 的value 而无论该协程运行在那个线程。
* hreadLocal.get用作thread-local变量的默认值,但是它可以被参数value覆盖
* 注意上下文元素不会追踪thread-local的修改 并且从不对应的上下文元素访问协程的thread-local会返回不确定的值 参见示例
*
*
* Yield挂起方法:
* public suspend fun yield(): Unit
*
* Yields the thread (or thread pool) of the current coroutine dispatcher to other coroutines to run if possible.
* yield代表让步 大致意思是 如果可能,让当前协程分发者或其他协程的other线程(或线程池)接着运行下面的代码
* This suspending function is cancellable.
* 该挂起方法可以被取消
* If the [Job] of the current coroutine is cancelled or completed when this suspending function is invoked or while
* this function is waiting for dispatch, it resumes with a [CancellationException].
* 如果当挂起方法被调用或该方法正在等待分发的时候,当前协程的Job被取消或者完成,最终会引发 CancellationException异常
*
* **Note**: This function always [checks for cancellation][ensureActive] even when it does not suspend.
*
*
* 输出
* [main @coroutine#1] current thread local value main
* [DefaultDispatcher-worker-1 @coroutine#2] launch current thread local value launch
* [DefaultDispatcher-worker-1 @coroutine#2] after yield current thread local value launch
* [main @coroutine#1] last current thread local value main
*
* 我这里的输出与视频不一样 yield之后没有切换线程
*/
class HelloKotlin11 {
}
import kotlinx.coroutines.*
/**
* 猜想一下这里的代码的输出
*/
private val threadLocal = ThreadLocal<String>()
private fun log(logMessage: String) = println("[${Thread.currentThread().name}] $logMessage")
fun main() = runBlocking {
threadLocal.set("main")
log("current thread local value ${threadLocal.get()}")
val job = launch (Dispatchers.Default){
threadLocal.set("AA")
log("current thread local value ${threadLocal.get()}")
yield()
log("current thread local value ${threadLocal.get()}")
}
job.join()
log("current thread local value ${threadLocal.get()}")
}
/**
* 输出
*
* [main] current thread local value main
* [DefaultDispatcher-worker-1] current thread local value AA
* [DefaultDispatcher-worker-1] current thread local value AA
* [main] current thread local value main
*
* 不明白threadLocal.asContextElement的意义了 不指定这个 线程里面的数据还是可以在协程间切换
* 那么asContextElement的意义何在
*/
class HelloKotlin11_1 {
}
asContextElement示例
/**
* asContextElement示例
*/
import kotlinx.coroutines.*
import java.util.concurrent.Executors
private fun log(logMessage: String?) = println("[${Thread.currentThread().name}] $logMessage")
fun main() = runBlocking {
val myThreadLocal = ThreadLocal<String?>()
val threadDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
// 下面的这句运行在主线程的协程1 myThreadLocal没有初始化 得到null
log("0" + myThreadLocal.get()) // Prints "null"
launch(Dispatchers.Default + myThreadLocal.asContextElement(value = "foo")) {
// 下面的这句切换线程 运行在DefaultDispatcher-worker-2线程 myThreadLocal刚初始化为foo 得到foo
log("1" + myThreadLocal.get()) // Prints "foo"
withContext(threadDispatcher) {
// 下面的这句再次切换线程池 运行在pool-1-thread-1 @coroutine#2 myThreadLocal的值延用外层的值 得到foo
log("2" + myThreadLocal.get()) // Prints "foo", but it's on other thread
}
}
// 下面的这句运行在主线程的协程1 主线程的myThreadLocal仍然没有初始化 得到null
log("3" + myThreadLocal.get()) // Prints "null"
//The context element does not track modifications of the thread-local variable, for example:
//上下文元素不会追踪thread-local值的修改
myThreadLocal.set("main")//修改主线程myThreadLocal的值
// 下面的这句再次切换线程池 运行在pool-1-thread-1 @coroutine#1
withContext(threadDispatcher) {
log("4" + myThreadLocal.get()) // Prints "main" // 与官网说的不一样 这里输出null???
myThreadLocal.set("UI")
}
// 下面的这句运行在主线程的协程1 主线程的myThreadLocal被初始化为main 得到main 与其他线程的值修改无关
log("5" + myThreadLocal.get()) // Prints "main", not "UI"
//Use `withContext` to update the corresponding thread-local variable to a different value, for example:
// 使用withContext更新对应线程的thread-local变量为不同的值
withContext(myThreadLocal.asContextElement(value = "foo")) {// 将主线程中的thread-local的值修改为foo
// 下面的这句运行在主线程的协程1 得到值为foo
log("6" + myThreadLocal.get()) // Prints "foo"
}
//Accessing the thread-local without corresponding context element leads to undefined value:
// 访问不对应上下文元素的thread-local 会导致得到一个不确定的值 这里还有问题
val tl = ThreadLocal.withInitial { "initial" }// 初始化ThreadLocal的初始值为initial
runBlocking {
// 下面的这句运行在主线程的协程3 "main @coroutine#3" 得到值为initial
log("7" + tl.get()) // Will print "initial"
// Change context
withContext(tl.asContextElement("modified")) {// 使用withContext更新”main @coroutine#3“的ThreadLocal值为modified
// // 下面的这句运行在主线程的协程3 "main @coroutine#3" 得到值为modified
log("8" + tl.get()) // Will print "modified"
}
// Context is changed again
log("9" + tl.get()) // <- WARN: can print either "modified" or "initial" // 这里我的输出一直都是initial???
}
threadDispatcher.close()
}
/*
我的输出
[main @coroutine#1] 0null
[main @coroutine#1] 3null
[DefaultDispatcher-worker-1 @coroutine#2] 1foo
[pool-1-thread-1 @coroutine#2] 2foo
[pool-1-thread-1 @coroutine#1] 4null
[main @coroutine#1] 5main
[main @coroutine#1] 6foo
[main @coroutine#3] 7initial
[main @coroutine#3] 8modified
[main @coroutine#3] 9initial
Process finished with exit code 0
*/
class HelloKotlin11_2 {
}