Kotlin 構文の上級 - コルーチン (1) コルーチンの基本

1. kotlin コルーチンの理解

コルーチンは新しい概念ではありませんが、非常に古い概念です。多くの言語がコルーチンをサポートしています。コルーチンの歴史と基本概念を理解するには、ブラウザにアクセスすることをお勧めします。ここでは、kotlin のコルーチンについてのみ説明します。効果。

コード実装の観点から見ると、kotlin コルーチンの最下層はスレッドを使用して実装されており、開発者が使用できる完全にカプセル化されたスレッド フレームワークです。kotlin のコルーチンは、スレッド上で実行される実行タスクとして理解でき、タスクは異なるスレッド間で切り替えることができます。1 つのスレッドで複数のコルーチンを同時に実行できます。概念図は次のとおりです。1 つのスレッドで複数のコルーチンを実行できます(タスク
の実行)、必要に応じて、スレッド 1 のコルーチンをスレッド 2 に切り替えて実行できます。Android の開発を反映して、ネットワーク リクエストやデータベース操作などの時間のかかるタスクは IO スレッドで実行され、データを取得するとページの更新操作がメイン スレッドで実行されます。
ここに画像の説明を挿入します

開発者の観点から: Kotlin コルーチンは、同期的な方法で非同期実行コードを記述し、スレッド切り替えコールバックの入れ子地獄を解決できますコルーチンが一時停止されているときにスレッドをブロックする必要はなく、ほぼ無料です。
これは Android 開発を反映しています。以前は、IO スレッドでデータを取得した後、メイン スレッドに戻って UI を更新したいと考えていました。通常はハンドラーまたは CallBack インターフェイスのコールバックを使用していました。その結果、上記の操作を連続して実行すると非常に時間がかかり、入れ子地獄に陥りやすくなり、コードの読みやすさや美しさに重大な影響を及ぼします。

2. コルーチンの作成方法

Android Studio でコルーチンを使用したいのですが、kotlin サポートの導入に加えて、2 つのコルーチン サポート ライブラリも導入する必要があります。

// 协程核心库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1"
// 协程Android支持库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1"

コルーチンの作成方法

2.1.) runBlocking: これは、新しいコルーチンを開始し、内部のコードが実行されるまでそれを呼び出すスレッドをブロックするトップレベル関数です。戻り値は汎用の T です。
public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T
2.2) CoroutineScope.launch: コルーチン スコープの拡張メソッド launch を通じてコルーチンを起動します。それを呼び出すスレッドはブロックされません。戻り値は Job です。
public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job
2.3) CoroutineScope.async: コルーチン スコープの非同期拡張メソッドを通じてコルーチンを開始します。これを呼び出すスレッドはブロックされず、戻り値は Deferred です。
public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T>

コルーチンを起動するには、2)と3)の2つの方法が一般的ですが、1)の方法はスレッドをブロックしてしまうため、開発では基本的にrunBlocking関数を使用しませんが、コードのデバッグには使用できます。
async と launch の違いについては、後で例を挙げて説明します:
戻り値が異なります: async 関数本体のコード式の最後の行は、結果として結果を返します。 generic T in Deferred. We can use other コルーチン関数ではこの実行結果が得られますが、launch ではそのような戻り値はありません。

サンプルコード: GlobalScope は、コルーチン ライブラリですでに提供されているコルーチン スコープです。

runBlocking {
    
    
    Log.e("协程","我们使用runBlocking启动了一个协程")
}
GlobalScope.launch {
    
    
    Log.e("协程","我们使用launch启动了一个协程")
}
GlobalScope.async {
    
    
    Log.e("协程","我们使用async启动了一个协程")
}

3. コルーチンのスコープとコルーチンのコンテキスト

コルーチンを作成する 3 つの方法のうち、起動と非同期ではどちらも、コルーチンを開くためにコルーチン スコープを使用する必要があると前述しました。

3.1 コルーチン スコープとは何ですか?

コルーチン スコープ CoroutineScope は、コルーチンの実行スコープです。
以下のように: CoroutineScope は、CoroutineContext という 1 つのインターフェイス属性のみを持つインターフェイスであるため、CoroutineScope は実際には CoroutineContext コルーチン コンテキストのカプセル化です。

public interface CoroutineScope {
    
    
    public val coroutineContext: CoroutineContext
}

3.2 コルーチンコンテキストとは何ですか?

CoroutineContext は、 coroutine のコンテキストを表します。コルーチン コンテキストを理解する前に、まず Context コンテキストを理解します。Android 開発者は、この属性についてよく理解しておく必要があります。アプリケーション、アクティビティなどはすべて Context として定義されます。では、このコンテキストをどのように理解すればよいのでしょうか? 例えば、学校の読解テストでは、文章の意味を理解し、何をすべきかを理解する必要があり、元の記事を置き換えて、前を見て後ろがどうなっているのかを理解する必要があります。 。コードに置き換えて、このコードを実行しました。また、このコードの実行方法と実行に必要な情報も理解する必要があります。この情報はコンテキストに保存されます。コンテキストをストレージのコンテナとして理解できます。構成コードの実行に必要な情報。したがって、CoroutineContext は、コルーチンを開始するために必要な構成情報です。

CoroutineContext のソース コード実装を見てみましょう。これはインターフェイスです。インターフェイス内には内部インターフェイス Element があります (このインターフェイスは CoroutineContext を継承しています。Element インターフェイスにはキーがあり、キーと値のペアに似ています)。キーを使用して要素を判断できます)。CoroutineContext
に対して get (キーに基づいて CoroutineContext を取得する)、plus (プラス演算子オーバーロードの戻り値は CoroutineContext です、つまり CoroutineContext です) などの操作を実行できることがわかります。 +CoroutineContext =CoroutineContext)、minusKey (キーに対応する CoroutineContext を削除)、watch これは本当に set 操作に似ています。実際、CoroutineContext は set 操作に似ているため、CoroutineContext には多くのサブクラスがあると推測できます。を追加および結合したり、minusKeys を減算してさまざまな CoroutineContext を形成したりできます。

public interface CoroutineContext {
    
    

    public operator fun <E : Element> get(key: Key<E>): E?

    public fun <R> fold(initial: R, operation: (R, Element) -> R): R

    public operator fun plus(context: CoroutineContext): CoroutineContext =
        if (context === EmptyCoroutineContext) this else 
            context.fold(this) {
    
     acc, element ->
                val removed = acc.minusKey(element.key)
                if (removed === EmptyCoroutineContext) element else {
    
    
    
                    val interceptor = removed[ContinuationInterceptor]
                    if (interceptor == null) CombinedContext(removed, element) else {
    
    
                        val left = removed.minusKey(ContinuationInterceptor)
                        if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
                            CombinedContext(CombinedContext(left, element), interceptor)
                    }
                }
            }

    public fun minusKey(key: Key<*>): CoroutineContext

    public interface Key<E : Element>

    public interface Element : CoroutineContext {
    
    

        public val key: Key<*>

        public override operator fun <E : Element> get(key: Key<E>): E? 
            @Suppress("UNCHECKED_CAST")
            if (this.key == key) this as E else null

        public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
            operation(initial, this)

        public override fun minusKey(key: Key<*>): CoroutineContext =
            if (this.key == key) EmptyCoroutineContext else this
    }
}
3.3 コルーチンのスコープとサブコルーチン

コルーチン内で別のコルーチンを開始できます。この方法で開かれたコルーチンはサブコルーチンです。
注意点は2つあります:
1.) 子コルーチンのコルーチン スコープは親コルーチン スコープのコルーチン コンテキストを継承します
2.) 親コルーチンがキャンセルされると、すべての子コルーチンもキャンセルされます

。以下、この 2 つの注意点を順番に説明します。

3.4 CoroutineContext のサブクラス

CoroutineContext には多くのサブクラスがあり、それぞれが異なる機能を持ち、それらが集まってコルーチン スコープ内の CoroutineContext を構成します。
まず、コルーチン スコープの coroutineContext を出力し、何が出力されるかを見てみましょう。

GlobalScope.launch{
    
    
    Log.e("协程的coroutineContext",this.coroutineContext.toString())
}
打印结果:
[StandaloneCoroutine{
    
    Active}@5a7652d, Dispatchers.Default]

これらのサブクラスを 1 つずつ理解し、コルーチンに対するそれらの影響と役割を理解します。

3.4.1)CoroutineDispatcher コルーチン スケジューラ

コルーチンはスレッドで実行され、他のスレッドに切り替えることができることはわかっていますが、CoroutineDispatcher は関連するコルーチンがどのスレッドで実行されるかを決定するため、CoroutineDispatcher はコルーチンのスレッド スケジューラとも呼ばれます。

public abstract class CoroutineDispatcher :
    AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
    
    

    @ExperimentalStdlibApi
    public companion object Key : AbstractCoroutineContextKey<ContinuationInterceptor, CoroutineDispatcher>(
        ContinuationInterceptor,
        {
    
     it as? CoroutineDispatcher })
}

Kotlin は 4 つのスケジューラーを提供します

public actual object Dispatchers {
    
    
public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
public val IO: CoroutineDispatcher = DefaultScheduler.IO
}

デフォルト: デフォルトのスケジューラである CPU 集中型のタスク スケジューラは、通常、いくつかの単純なコンピューティング タスク、または実行時間の短いタスクを処理します。たとえば、データ計算
IO: IO スケジューラ、IO 集約型タスク スケジューラ。IO 関連の操作の実行に適しています。例: ネットワーク リクエスト、データベース操作、ファイル操作など
メイン: UI スケジューラ、UI プログラミング プラットフォームでのみ意味があり、Android のメイン スレッドなどの UI を更新するために使用されます 制限なし: 制限のないスケジューラ、スケジューラなし、
現在コルーチンは任意のスレッドで実行できます。

上記の出力から、GlobalScope のコルーチン スケジューラが Dispatchers.Default であることがわかります。前に起動メソッドと非同期メソッドを確認したとき、最初のパラメータは context: CoroutineContext であることがわかりました。はい、ここから必要なコンテキストを渡すことができ、コルーチン スコープ内のコンテキストが上書きされます。
次のように: IO スレッドで実行するコルーチンを開始したいと考えています。

GlobalScope.launch(Dispatchers.IO){
    
    
    Log.e("协程的coroutineContext",this.coroutineContext.toString())
}
打印结果:
[StandaloneCoroutine{
    
    Active}@db90566, Dispatchers.IO]

では、コルーチンの実行中にスレッドを変更したい場合はどうすればよいでしょうか? 最も一般的には、ネットワーク リクエストは IO スレッドで行われ、ページの更新はメイン スレッドで行われます。Kotlin は、コルーチンのコンテキストを変更してコードを実行するためのトップレベル関数 withContext を提供します。

public suspend fun <T> withContext(
    context: CoroutineContext,
    block: suspend CoroutineScope.() -> T
): T 

サンプルコード:

GlobalScope.launch(Dispatchers.Main) {
    
    
    val result = withContext(Dispatchers.IO) {
    
    
        //网络请求
        "返回结果"
    }
    mBtn.text = result
}
3.4.2)CoroutineName コルーチン名

コルーチン名: 名前のとおり、コルーチンに名前を付けるものですが、これについては何も言うことはなく、コルーチンをより区別しやすくするために使用されます。

public data class CoroutineName(

    val name: String
) : AbstractCoroutineContextElement(CoroutineName) {
    
    

    public companion object Key : CoroutineContext.Key<CoroutineName>

    override fun toString(): String = "CoroutineName($name)"
}

サンプルコード:

GlobalScope.launch(Dispatchers.Main + CoroutineName("主协程")) {
    
    
    Log.e("协程的coroutineContext",this.coroutineContext.toString())
}
打印结果:
[CoroutineName(主协程), StandaloneCoroutine{
    
    Active}@288ff9, Dispatchers.Main]

コルーチン名を使用してサブコルーチンを確認するときに注意すべき最初の点: サブコルーチンのコルーチン スコープは、親コルーチン スコープのコルーチン コンテキストを継承します。上記のコードでサブコルーチンを開始します

GlobalScope.launch(Dispatchers.Main + CoroutineName("主协程")) {
    
    
    Log.e("协程的coroutineContext" , this.coroutineContext.toString())
    launch {
    
    
        Log.e("协程的coroutineContext2" , this.coroutineContext.toString())
    }
}

出力結果は次のとおりです。 子コルーチンが実際に親コルーチンと同じ CoroutineName を出力していることがわかり、親コルーチンのコルーチン コンテキストが子コルーチンに伝播されたことがわかります。

协程的coroutineContext: [CoroutineName(主协程), StandaloneCoroutine{
    
    Active}@288ff9, Dispatchers.Main]
协程的coroutineContext2: [CoroutineName(主协程), StandaloneCoroutine{
    
    Active}@b95b3e, Dispatchers.Main]

コードを少し変更してみましょう。

GlobalScope.launch(Dispatchers.Main + CoroutineName("主协程")) {
    
    
    Log.e("协程的coroutineContext" , this.coroutineContext.toString())
    launch(CoroutineName("子协程")) {
    
    
        Log.e("协程的coroutineContext2" , this.coroutineContext.toString())
    }
}

出力結果は次のようになります。 子コルーチンによって設定されたコルーチン コンテキストは、親コルーチンから継承されたコンテキストを上書きします。

协程的coroutineContext: [CoroutineName(主协程), StandaloneCoroutine{
    
    Active}@288ff9, Dispatchers.Main]
协程的coroutineContext2: [CoroutineName(子协程), StandaloneCoroutine{
    
    Active}@8aced9f, Dispatchers.Main]
3.4.3) ジョブとコルーチンのライフサイクル

先ほど 2 つの拡張関数 launch と async を見たとき、launch の戻り結果は Job であり、async の戻り結果は Deferred であることがわかります。Deferred は実際には Job のサブクラスです。

public interface Job : CoroutineContext.Element 
public interface Deferred<out T> : Job 

では、仕事とは何でしょうか?
コルーチンを起動するとJobオブジェクトを取得することができ、Jobオブジェクトを通じてコルーチンのライフサイクル状態の検出やコルーチンの操作(コルーチンのキャンセルなど)を行うことができますJob はコルーチンそのものとして大まかに理解できます。
コルーチンのライフサイクル: コルーチンが作成された後、コルーチンは New (新しい) 状態になり、コルーチンが開始された (start() メソッドが呼び出される) 後、Active (アクティブ) 状態になります。コルーチンとすべてのサブコルーチンがタスクを完了すると、Completed 状態になります。(Complete) 状態になります。コルーチンがキャンセルされた後 (cancel() メソッドが呼び出された後)、Canceled 状態になります
。コルーチンのステータスを確認するジョブ:
isActive はコルーチンがアクティブかどうかを判断するために使用
されます isCancelled はコルーチンが終了したかどうか
を判断するために使用されます
コルーチンのステータスを取得する以外にも、使用できる関数は多数ありますコルーチンを操作するには、次のようにします。
cancel() コルーチンをキャンセルします。
start() はコルーチンを開始します。
await() はコルーチンの実行の完了を待ちます

コルーチンのライフサイクルを確認してみましょう。

GlobalScope.launch {
    
    
    val job = launch(CoroutineName("子协程")) {
    
    

    }
    Log.e("子协程的状态","${
      
      job.isActive} ${
      
      job.isCancelled} ${
      
      job.isCompleted}")
    delay(1000)
    Log.e("子协程的状态2","${
      
      job.isActive} ${
      
      job.isCancelled} ${
      
      job.isCompleted}")
}
打印结果:
子协程的状态: true false false
子协程的状态2: false false true
GlobalScope.launch {
    
    
    val job = launch(CoroutineName("子协程")) {
    
    
        delay(5000)
    }
    Log.e("子协程的状态","${
      
      job.isActive} ${
      
      job.isCancelled} ${
      
      job.isCompleted}")
    job.cancel()
    Log.e("取消后子协程的状态","${
      
      job.isActive} ${
      
      job.isCancelled} ${
      
      job.isCompleted}")
}
打印结果:
子协程的状态: true false false
取消后子协程的状态: false true false

コルーチンのライフサイクルを使用して、サブコルーチンの 2 番目のノートを確認してみましょう。親コルーチンがキャンセルされると、すべてのサブコルーチンもキャンセルされます。

var childJob : Job? = null
val parentJob = GlobalScope.launch {
    
    
    childJob = launch(CoroutineName("子协程")) {
    
    
        delay(5000)
    }
}
Log.e("父协程的状态" , "${
      
      parentJob.isActive} ${
      
      parentJob.isCancelled} ${
      
      parentJob.isCompleted}")
Handler().postDelayed(Runnable {
    
    
    Log.e("子协程的状态" ,
        "${
      
      childJob?.isActive} ${
      
      childJob?.isCancelled} ${
      
      childJob?.isCompleted}")
    parentJob.cancel()
    Log.e("父协程的状态" ,
        "${
      
      parentJob.isActive} ${
      
      parentJob.isCancelled} ${
      
      parentJob.isCompleted}")
    Log.e("子协程的状态" ,
        "${
      
      childJob?.isActive} ${
      
      childJob?.isCancelled} ${
      
      childJob?.isCompleted}")
} , 1000)
打印结果如下:可以看到父协程取消以后,子协程也取消了。
父协程的状态: true false false
子协程的状态: true false false
父协程的状态: false true false
子协程的状态: false true false
3.4.4) CoroutineExceptionHandler コルーチン例外処理

コードを書いていると必ず異常事態に遭遇するもので、例外処理には通常try...catchを使いますが、抜け漏れが発生するのは避けられません。CoroutineExceptionHandler は、コルーチンでの例外のキャッチに特化したクラスです。コルーチンで発生した例外は、CoroutineExceptionHandler の handleException メソッドによってキャッチされ、処理のために返されます。

public interface CoroutineExceptionHandler : CoroutineContext.Element {
    
    
    public companion object Key : CoroutineContext.Key<CoroutineExceptionHandler>

    public fun handleException(context: CoroutineContext, exception: Throwable)
}

handleException は 2 つのパラメータを返します。最初のパラメータは例外が発生したコルーチンで、2 番目のパラメータは発生した例外です。
例は次のとおりです。NullPointerException 例外を手動でスローし、CoroutineExceptionHandler を作成してコルーチンに割り当てます。

val exceptionHandler = CoroutineExceptionHandler {
    
     coroutineContext, throwable ->
    Log.e("捕获异常", "${
      
      coroutineContext[CoroutineName]}$throwable")
}

GlobalScope.launch(Dispatchers.Main + CoroutineName("主协程")+exceptionHandler) {
    
    
    Log.e("协程的coroutineContext",this.coroutineContext.toString())
    throw NullPointerException()
}
打印结果:
协程的coroutineContext: [CoroutineName(主协程), com.jf.simple.ThirdActivity$onCreate$$inlined$CoroutineExceptionHandler$1@288ff9, StandaloneCoroutine{
    
    Active}@b95b3e, Dispatchers.Main]

捕获异常: CoroutineName(主协程) :java.lang.NullPointerException
3.4.5) ContinuationInterceptor コルーチンインターセプター
public interface ContinuationInterceptor : CoroutineContext.Element {
    
    

    companion object Key : CoroutineContext.Key<ContinuationInterceptor>
}

名前のとおり、コルーチンをインターセプトするために使用されますが、関数を中断する原理が含まれており、日常の開発では比較的まれに使用されるため、ここでは拡張しません。

4. コルーチンの起動モード

launch および async 拡張関数を見ると、2 番目のパラメーター start: CoroutineStart があります。このパラメーターの意味は、コルーチンの起動モードです。

public enum class CoroutineStart {
    
    
DEFAULT,
LAZY,
ATOMIC,
UNDISPATCHED;
}

CoroutineStart は 4 つの型を持つ列挙クラスであることがわかります。
DEFAULT はデフォルトの起動モードです。スケジューリングはコルーチンの作成後すぐに開始されます。すぐに実行されるのではなく、すぐにスケジュールされることに注意してください。実行前にキャンセルされる場合があります。
LAZY 遅延起動モードでは、作成後にスケジューリング動作が行われず、実行が必要になるまでスケジューリングは行われません。スケジュールは、ジョブの start、join、または await 関数を手動で呼び出す必要がある場合にのみ開始されます。
ATOMICはコルーチン作成後すぐにスケジューリングを開始しますが、DEFAULTモードとは異なり、コルーチン起動後、キャンセル操作に応答する前に最初の一時停止ポイントまで実行する必要があります。
このモードでは、UNDISPATCHED コルーチンは、最初の一時停止ポイントに到達するまで、現在のスレッドで直接実行を開始します。ATOMIC とよく似ていますが、UNDISPATCHED はスケジューラの影響を大きく受けます。

サンプル コード:
DEFAULT: コードはすぐに出力され、コルーチンが作成直後にスケジュールされていることを示します。

GlobalScope.launch {
    
    
    Log.e("default启动模式", "协程运行")
}

LAZY: start() メソッドが呼び出される前は出力されず、start() メソッドが呼び出された後にコードが出力されます。コルーチンの説明は作成後にスケジュールされないため、手動で開始する必要があります。

val lazyJob = GlobalScope.launch(start = CoroutineStart.LAZY){
    
    
    Log.e("lazy启动模式", "协程运行")
}
//lazyJob.start()

アトミック: コルーチンの実行後、最初の一時停止された関数に到達するまでは cancel() メソッドに応答しません。

val atomicJob = GlobalScope.launch(start = CoroutineStart.ATOMIC) {
    
    
    Log.e("atomic启动模式" , "运行到挂起函数前")
    delay(100)
    Log.e("atomic启动模式" , "运行到挂起函数后")
}
atomicJob.cancel()
打印结果:
atomic启动模式: 运行到挂起函数前

UNDISPATCHED: 結果は ATOMIC に非常に似ていることがわかりますが、ATOMIC と UNDISPATCHED が開発で使用されることは比較的まれであるため、ここではあまり区別しません。興味がある場合は、サブコルーチンを開いて、スケジューラでコードを実行して違いを確認します。

5.サスペンド機能

前述しました: kotlin コルーチンの最大の利点は、非同期コードを同期的に記述できることであり、これはサスペンション関数によって実現されます。

キーワードsuspendで修飾された関数をサスペンション関数と呼びますが、この関数はコルーチンや他のサスペンション関数内でのみ呼び出すことができます。サスペンド関数の特徴は「サスペンドとリカバリ」で、コルーチンがサスペンド関数に遭遇するとコルーチンは一時停止され、サスペンド関数が実行されるとコルーチンは中断されたところから再開して再度実行されます。
サスペンションはノンブロッキング サスペンションであり、スレッドをブロックしません。リカバリでは手動でリカバリする必要はありませんが、コルーチンが自動的にリカバリを行います。

ここでは、サスペンド関数の使用法を理解するために 2 つの例を示します。(機能停止の原則についてはここでは説明しません。後で特別な章を開きます。)

5.1) 非同期コードを順次実行する

サンプル コード 1: 2 つの一時停止関数を定義します。1 つは 1 秒の遅延、もう 1 つは 2 秒の遅延です (ネットワーク リクエストをシミュレートするため)。最終的には両方とも整数を返し、結果を加算します。コルーチンを使用しないと遅延が異なるため、結果を取得するにはコールバックのようなメソッドを使用する必要があります。しかし、コルーチンを使用する場合は異なります。出力結果から、コードが完全に順番に実行されていることがわかります。measureTimeMillis メソッドで実行時間を測定できます。実行時間は 3 秒を少し超えていることがわかります。つまり、プロセス全体は次のようになります。これ。コルーチンは returnNumber1() まで実行され、ハング関数であることが検出されます。コルーチンはハングし、returnNumber1() が完了するまで待機します。returnNumber1() が完了するまでに 1 秒かかります。コルーチンは returnNumber1 の場所に戻ります。 () が呼び出され、結果を取得し、続行します。実行すると、行が returnNumber2() に到達し、ハング関数であることが検出され、コルーチンがハングし、returnNumber2() が完了するのを待ちます。returnNumber2() が完了するまでに 2 秒かかります。 )、コルーチンは returnNumber2() が呼び出された場所に戻り、実行を継続します。

suspend fun returnNumber1() : Int {
    
    
    delay(1000L)
    Log.e("returnNumber1" , "调用了returnNumber1()方法")
    return 1
}

suspend fun returnNumber2() : Int {
    
    
    delay(2000L)
    Log.e("returnNumber1" , "调用了returnNumber2()方法")
    return 2
}
GlobalScope.launch {
    
    
    val time = measureTimeMillis {
    
    
        val number1 = returnNumber1()
        Log.e("number1" , "需要获取number1")
        val number2 = returnNumber2()
        Log.e("number2" , "需要获取number2")
        val result = number1 + number2
        Log.e("执行完毕" , result.toString())
    }
    Log.e("运行时间",time.toString())
}
打印结果:
returnNumber1: 调用了returnNumber1()方法
number1: 需要获取number1
returnNumber1: 调用了returnNumber2()方法
number2: 需要获取number2
执行完毕: 3
运行时间: 3010
5.2) async は同時実行性を実装します

中断関数は現在のコルーチンを中断し、他のコルーチンには影響しないことに注意してください。
上記のコードを変更して、2 つの中断関数を 2 つのサブコルーチンに配置しましょう。最終結果は、await() を使用して取得されます。await() の関数コルーチンの実行が完了して戻り値を取得するのを待つ方法です。launchとasyncの違いについては前に述べましたが、一方は実行結果の戻り値を持ち、もう一方は持たないので、ここではasyncを使用します。
サンプル コード: ここでは、Async を使用して 2 つのサブコルーチンを開きます。両方のサブコルーチンにはサスペンド関数があるため、両方のサブコルーチンはサスペンドされますが、それらの親コルーチンは await() サスペンド関数を呼び出す前にサスペンドされません。一時停止されているため、正常に実行できます。2 つのサブコルーチンは同時に実行されます。最終的な実行時間は印刷によっても確認できます。コードの実行にはわずか 2 秒かかります。

GlobalScope.launch(Dispatchers.Main) {
    
    
    val time = measureTimeMillis {
    
    
        val deferred1 = async {
    
    
            Log.e("--" , "子协程1运行开始")
            returnNumber1()
        }
        Log.e("--" , "开始运行第二个协程")
        val deferred2 = async {
    
    
            Log.e("--" , "子协程2运行开始")
            returnNumber2()
        }
        Log.e("--" , "开始计算结果")

        val result = deferred1.await() + deferred2.await()
        Log.e("执行完毕" , result.toString())

    }
    Log.e("运行时间" , time.toString())
}

打印结果如下:
开始运行第二个协程
开始计算结果
子协程1运行开始
子协程2运行开始
returnNumber1: 调用了returnNumber1()方法
returnNumber1: 调用了returnNumber2()方法
执行完毕: 3
运行时间: 2009

次のセクションでは、最も効果的な方法で機能を停止する原理について説明します。

おすすめ

転載: blog.csdn.net/weixin_43864176/article/details/126234790