協程

runBlocking launch async

runBlocking是主協程,裡面包含了job1與job2二個子協程。
runBlocking會等待job1與job2執行完畢。
launch、async都是同時執行,job2不用等待job1執行完畢才執行。

1
2
3
4
5
6
7
8
9
10
  fun coroutin01() = runBlocking {
    val job1 = launch {
      delay(200)
      println("job1 finished")
    }
    val job2 = async {
      delay(200)
      println("job2 finished")
    }
  }
job1 finished
job2 finished

await() 傳回值

語法

1
job2.await()

async會傳回最後一行執行結果,並讓主協程等待job2子協程執行完畢。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  fun coroutin01() = runBlocking {
    val job1 = launch {
      delay(200)
      println("job1 finished")
    }
    val job2 = async {
      delay(200)
      println("job2 finished")
      // 回傳結果
      "job2 result"
    }
    // 印出回傳結果
    println(job2.await())
  }
job1 finished
job2 finished
job2 result

await()執行完才輪其它協程

job1.await()

job1先執行,job1執行完,才輪job2與job3,job2、job3同時執行。(程式碼移動到那一行,就執行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  fun coroutin01() = runBlocking {
    val job1 = launch {
      delay(200)
      println("job1 finished")
    }
    // job1 先執行
    job1.await()
    val job2 = async {
      delay(200)
      println("job2 finished")
    }
    val job3 = async {
      delay(200)
      println("job3 finished")
    }
  }

join()執行完才輪其它協程

job1.join()

job1先執行,job1執行完,才輪job2與job3,job2、job3同時執行。(程式碼移動到那一行,就執行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  fun coroutin01() = runBlocking {
    val job1 = launch {
      delay(200)
      println("job1 finished")
    }
    job1.join()
    val job2 = async {
      delay(200)
      println("job2 finished")
    }
    val job3 = async {
      delay(200)
      println("job3 finished")
    }
  }

逐步執行與同時執行

沒加async是逐步執行

measureTimeMillis()是計算執行時間。
doOne()、doTwo()都是suspend 協程函式。
但在這邊會doOne()執行完,才輪到doTwo()執行。
所以耗時2秒多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  fun coroutin02() = runBlocking {
    val time = measureTimeMillis {
      val one = doOne()
      val two = doTwo()
      println("result = ${one + two}")
    }
    println("total in $time ms")
  }
  private suspend fun doOne():Int {
    // 停1秒
    delay(1000)
    return 10
  }
  private suspend fun doTwo():Int {
    // 停1秒
    delay(1000)
    return 20
  }
result = 30
total in 2022 ms

加async是同時執行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  fun coroutin02() = runBlocking {
    val time = measureTimeMillis {
      val one = async{doOne()}
      val two = async{doTwo()}
      println("result = ${one.await() + two.await()}")
    }
    println("total in $time ms")
  }
  private suspend fun doOne():Int {
    delay(1000)
    return 10
  }
  private suspend fun doTwo():Int {
    delay(1000)
    return 20
  }
result = 30
total in 1020 ms

await()傳回值錯誤範例

await()放在async{}後面,會導致doOne()執行完畢,doTwo()才能執行,變成逐步執行,而不是同時執行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  fun coroutin02() = runBlocking {
    val time = measureTimeMillis {
      val one = async{doOne()}.await()
      val two = async{doTwo()}.await()
      println("result = ${one + two}")
    }
    println("total in $time ms")
  }
  private suspend fun doOne():Int {
    delay(1000)
    return 10
  }
  private suspend fun doTwo():Int {
    delay(1000)
    return 20
  }
result = 30
total in 2024 ms

launch async父類別

launch繼承Job

1
2
3
4
5
public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job 

async繼承Deferred,Deferred繼承Job,所以async父類別也是job。

1
2
3
4
5
public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> 
1
public interface Deferred<out T> : Job

協程取消與啟動模式

啟動模式,也就是下面的start參數,async()與launch()都有start

1
2
3
4
5
public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job 

start = DEFAULT

job 10秒後才會執行,但在執行前就被主協程取消,所以10秒後不會印出job finish。

1
2
3
4
5
6
7
8
9
  fun coroutin03() = runBlocking {
    val job = launch(start = CoroutineStart.DEFAULT) {
      println("test")
      delay(10000)
      println("job finish")
    }
    job.cancel()
    println("job cancel")
  }
job cancel

start = ATOMIC

delay()是suspend函式,若start設為ATOMIC,即便主協程把子協程取消,子協程會執行到第1個suspend函式,才會執行取消,在suspend函式之前的程式碼都會執行。

下面程式執行結果仍會印出test,因為子協程job是到delay()才被取消,delay()之前的程式碼都會執行。

1
2
3
4
5
6
7
8
9
  fun coroutin03() = runBlocking {
    val job = launch(start = CoroutineStart.ATOMIC) {
      println("test")
      delay(10000)
      println("job finish")
    }
    job.cancel()
    println("job cancel")
  }
job cancel
test

start = Lazy

start設為Lazy,遇到start()、await()、join()才會建立協程,用到的時候才會建立函式不占記憶體function stack空間。

但因為我的子協程內容過短,會讓編譯器認為沒內容,編譯會失敗,使用runTest取代runBlocking當主協程。

build.gradle要加上以下內容。

1
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")

要import runTest

1
import kotlinx.coroutines.test.runTest

主要程式內容

1
2
3
4
5
6
7
8
9
  @Test
  fun coroutin03() = runTest {
    val job = async(start = CoroutineStart.LAZY) {
      println("job finish")
    }
    // 執行到start() await() join(),才建立job協程
    job.start()
    // job.await()
  }
job finish

UNDISPATCHED執行緒切換

start = DEFAULT

以下start為DEFAULT預設,context協程調度器設為IO,IO為在背景處理耗時操作。

job協程建立時,從Thread Pool中取出IO執行緒,在此運行。

  1. coroutin04()一開始執行緒為Test worker。
  2. 執行job協程,執行緒是在IO執行緒(DefaultDispatcher-worker-1)。
  3. 呼叫suspend函式後,執行緒在IO執行緒(DefaultDispatcher-worker-1)。
1
2
3
4
5
6
7
8
9
  fun coroutin04() = runTest {
    println("目前thread = " + Thread.currentThread().name)
    val job = async(context = Dispatchers.IO, start = CoroutineStart.DEFAULT) {
      println("協程 thread = " + Thread.currentThread().name)
      // 呼叫suspend函式
      delay(1000)
      println("suspend resume thread = " + Thread.currentThread().name)
    }
  }
目前thread = Test worker @kotlinx.coroutines.test runner#2
協程 thread = DefaultDispatcher-worker-1 @coroutine#3
suspend resume thread = DefaultDispatcher-worker-1 @coroutine#3

start = UNDISPATCHED

以下start為UNDISPATCHED,context協程調度器設為IO,IO為在背景處理耗時操作。

job協程建立時,與coroutin03()的執行緒相同都是Test worker。
呼叫suspend函式後,才會轉成IO執行緒(DefaultDispatcher-worker-1)執行。

  1. coroutin03()的主執行緒的名字為Test worker。
  2. job協程仍在主執行緒(Test work)執行。
  3. 呼叫suspend函式後,執行緒切換至IO執行緒(DefaultDispatcher-worker-1)。
1
2
3
4
5
6
7
8
9
  fun coroutin03() = runTest {
    println("目前thread = " + Thread.currentThread().name)
    val job = async(context = Dispatchers.IO, start = CoroutineStart.UNDISPATCHED) {
      println("協程 thread = " + Thread.currentThread().name)
      // 呼叫suspend函式
      delay(1000)
      println("suspend resume thread = " + Thread.currentThread().name)
    }
  }
目前thread = Test worker @kotlinx.coroutines.test runner#2
協程 thread = Test worker @coroutine#3
suspend resume thread = DefaultDispatcher-worker-1 @coroutine#

執行

DEFAULT、ATOMIC、LAZY,執行到子協程不會立刻執行,只會先開始建立。
UNDISPATCHED會立刻建立協程,並立刻執行。

results matching ""

    No results matching ""