Android Kotlin コルーチンのチャネルの使用

チャネルは、複数のコルーチン間で単一の値を転送する便利な方法を提供します。チャネルは、ストリームで値を送信する方法を提供します。

簡単に言うと、Channel は複数のコルーチン間の通信に使用され、コルーチン間の連携をよりスムーズにします。

メインコンテンツ:

1. チャンネルの基本的な使い方

Channel は BlockingQueue とよく似ていますが、ブロッキング put(…) オペレーションを置き換えて保留中の send(…) を提供することと、ブロッキング take(…) オペレーションを置き換えて保留中の accept(…) を提供するという点が 1 つ異なります。

簡単に言うと、チャネルを作成しデータを送信するために送信しデータを受信するために受信することです

//创建Channel
val channel = Channel<Int>()
launch {
    // send发送数据
    for (x in 1..5) channel.send(x * x)
}
// receive接收数据
repeat(5) { println(channel.receive()) }
println("Done!")

印刷結果

1
4
9
16
25
Done!

2. チャネルバッファ(容量)

上記の例に示されているチャネルはすべてバッファリングされていません。send が最初に呼び出された場合は、receive が呼び出されるまで一時停止され、receive が最初に呼び出された場合は、send が呼び出されるまで一時停止されます。

Channel() ファクトリ関数とプロデュース ビルダーは、バッファ サイズを指定するオプションのパラメータ容量を渡します。バッファリングにより、送信者は一時停止される前に複数の要素を送信できます。

バッファがいっぱいで、受信が呼び出されなかった場合、送信はハングします。


val channel = Channel<Int>(4) // 启动带缓冲的通道
val sender = launch { // 启动发送者协程
    repeat(10) {
        println("Sending $it") 
        channel.send(it) 
    }
}
// 没有接收到东西……只是等待……
delay(1000)
sender.cancel() // 取消发送者协程

印刷結果

Sending 0
Sending 1
Sending 2
Sending 3
Sending 4

結果を出力すると、最初の 4 つの要素がバッファに追加され、5 番目の要素を送信しようとしているときに送信者がハングしていることがわかります。

その他の固定タイプの容量

  • RENDEZVOUS、デフォルトのタイプ。データ送信後、受信がない場合はハングします。
  • CONFLATED、新しい要素が古い要素を置き換え、受信は常に最新のデータを取得します
  • UNLIMITED、バッファに制限はなく、データはいつでも送信できます。
  • BUFFERED は要素サイズを指定します。デフォルトは 64 で、いっぱいになると送信は一時停止されます (プログラム パラメータで変更可能)。

3. 反復チャネル

チャネルはシーケンスに似ており、読み取り時にチャネル反復子 (イテレータ) を直接取得できます。

イテレータ(iterator)やforループを通じて、Channelで生成された複数の要素を受け取ることができます。

 /**
     * 不限制容量
     *
     * 通过迭代器遍历,获取数据
     */
    fun simpleChannel3() {
        val channel = Channel<Int>(Channel.UNLIMITED)
        launch {
            // 这里可能是消耗大量 CPU 运算的异步逻辑,我们将仅仅做 5 次整数的平方并发送
            for (x in 1..5) channel.send(x * x)
        }
        // 这里我们打印了 5 次被接收的整数:

        launch {
            val iterator = channel.iterator()
            while (iterator.hasNext()) {
                Log.d("liu", "Channel send :${iterator.next()}")
                delay(1000)
            }

//            //这种方式也可以
//            for (data in channel) {
//                Log.d("liu", "Channel send :${data}")
//                delay(1000)
//            }
        }

        Log.d("liu", "Done!")
    }

4、プロデュースと俳優

これまでのすべての例では、チャネルを作成しました。次に、さまざまなコルーチンを介して操作します。では、プロデューサーとコンシューマーを構築する便利な方法はあるのでしょうか?

ここに画像の説明を挿入

プロデュース メソッドを通じてプロデューサー コルーチンを開始し、ReceiceChannel を返すことができ、他のコルーチンはこの Channel を使用してデータを受信できます。逆に、アクターを使用してコンシューマ コルーチンを開始し、SendChannel を返し、他のコルーチンがこのチャネルを通じてデータを送信することができます。

ただし、作成者が使用しているバージョンでは、これら 2 つの API はマークされており、まだ完全なステータスに変換されていません。

  • 生産
fun simpleChannelProduct() {
		//scope.produce。这里直接委托使用了MainScope
		//@ExperimentalCoroutinesApi  带标记的
        val receiveChannel = produce<Int> {
            var i = 0
            repeat(10) {
                delay(100)
                send(i++)
            }
        }
        launch {
            for (data in receiveChannel) {
                Log.d("liu", "Channel receive :${data}")
            }
        }
    }

Produce と呼ばれる便利なコルーチン ビルダーを使用すると、プロデューサー側での処理を簡単に行うことができ、拡張関数 ConsumerEach を使用してコンシューマー側の for ループを置き換えます。

 fun simpleChannelProduct2() {
        val receiveChannel = produce<Int> {
            var i = 0
            repeat(10) {
                delay(100)
                send(i++)
            }
        }
        launch {
            receiveChannel.consumeEach {value ->
                Log.d("liu", "Channel receive :${value}")
            }
        }
    }

  • 俳優
fun simpleChannelSend() {
		//@ObsoleteCoroutinesApi  带标记的
        val sendChannel = actor<Int> {
            while (true) {
                val data = receive()
                Log.d("liu", "Channel receive :${data}")
            }
        }
        launch {
            for (i in 0..3) {
                delay(100)
                sendChannel.send(i++)
            }
        }
    }

5. チャネルチャネルの閉鎖

プロデュースとアクターによって返されたチャネルは、対応するコルーチンが実行されると閉じられるため、チャネルはホット データ フローとも呼ばれます。

Channel の場合、Close メソッドを呼び出すと、新しい要素の受け入れがすぐに停止されます。つまり、isClosedForSend はこの時点ですぐに true を返します。また、Channel バッファーの存在により、現時点では一部のデータが処理されない可能性があるため、すべての要素が読み取られて isColsedForReceive が true を返すまで、Channel は閉じられません。

瞬時に 3 件送信し、送信後すぐに閉じます。受付する場合は1秒に1回受け付けます。このように、閉じているときにChannel内にデータがあるので、Channelのステータスを見てみましょう

サンプルコード、

 /**
     * Channel的关闭
     */
    fun simpleChannelClose() {
        val channel = Channel<Int>(3)
        launch {
            List(3) {
                channel.send(it)
                Log.d("liu", "Channel send :${it}")
            }
            channel.close()

            Log.d("liu", "Channel isClosedForSend :${channel.isClosedForSend}")
            Log.d("liu", "Channel isClosedForReceive :${channel.isClosedForReceive}")
            Log.d("liu", "Channel isEmpty :${channel.isEmpty}")
        }

        launch {
            for (data in channel) {
                Log.d("liu", "Channel receive :${data}")
                delay(1000)
            }

            Log.d("liu", "Channel isClosedForSend :${channel.isClosedForSend}")
            Log.d("liu", "Channel isClosedForReceive :${channel.isClosedForReceive}")
            Log.d("liu", "Channel isEmpty :${channel.isEmpty}")
        }
    }

六、放送チャンネル

前述したように、Channel では送信側と受信側は 1 対多の関係にあり、データ処理自体の観点から見ると、受信側は複数あっても、同じ要素は 1 つの受信側でのみ受信されます。ブロードキャストの場合はそうではなく、複数の受信機が受信でき、相互排他はありません。


  /**
     * BroadcastChannel
     */

    fun simpleBroadcastChannel() {
        val broadcastChannel = BroadcastChannel<Int>(Channel.BUFFERED)
        //2种创建方式
//        val channel = Channel<Int>()
//        val broadcastChannel = channel.broadcast(Channel.BUFFERED)


        launch {
            List(3) {
                delay(1000)
                broadcastChannel.send(it)
            }
        }

        List(3) {
            launch {
                val receiveChannel = broadcastChannel.openSubscription()
                for (i in receiveChannel) {
                    Log.d("liu", "$it : Channel is :${i}")
                }
            }
        }
    }


7、多重化(実験的性質)

データ通信システムやコンピュータネットワークシステムでは、伝送媒体の帯域幅や容量が単一の信号の伝送要求を上回ることが多く、通信回線を有効に利用するために、1つのチャネルで複数の信号を同時に伝送することが望まれる、いわゆる多重化技術です。

中国語ドキュメント:クリック

多重化を待つ

2 つの API はそれぞれネットワークとローカル キャッシュからデータを取得し、最初に返されると予想される方が最初に使用されます。



    suspend fun CoroutineScope.obtainDataFromNetwork() = async(Dispatchers.IO) {
        delay(500)
        "I'm From Network"
    }

    fun CoroutineScope.obtainDataFromCache() = async(Dispatchers.IO) {
        delay(300)
        "I'm From Cache"
    }

    fun awaitTest() {
        launch {
            val data = obtainDataFromNetwork()
            val cacheData = obtainDataFromCache()

            /**
             * 拿到最快的返回结果,哪个返回快用哪个
             */
            val dataResult = select<Test> {
                cacheData.onAwait { Test(it, true) }
                data.onAwait { Test(it, false) }
            }

            dataResult.data?.let {
                Log.d("liu", "$it ")
            }
        }
    }


チャネル多重化


fun channelTest() {
        val channels = listOf(Channel<Int>(), Channel<Int>())

        launch {
            delay(100)
            channels[0].send(0)
        }
        launch {
            delay(200)
            channels[1].send(1)
        }

        launch {
            val result = select<Int> {
                channels.forEach { channel ->
                    channel.onReceive { it }
                }
                Log.d("liu", "$result ")
            }
        }
    }


選択句

どのようなイベントが選択できるのでしょうか?実際、選択できるすべてのタイプのイベント SelectClause N:

  • SelectClause0: 対応するイベントに戻り値 (join など) がない場合、onJoin はイベント SelectClauseN タイプです。onJoin を使用すると、パラメーターのない関数になります。
  • SelectClause1: 対応するイベントには戻り値があり、以前の onAwait、onReceive はすべてこれに属します
  • SelectClause2: 対応するイベントには戻り値があり、追加のパラメーターが必要です。たとえば、Channel.onSend には 2 つのパラメーターがあり、1 は送信される値を示す Channel のデータ型の値、2 は送信が成功したときのコールバック パラメーターです。

一時停止関数が選択関数をサポートしているかどうかを確認したい場合は、対応する SelectClauseN タイプのコールバックがあるかどうかを確認するだけで済みます。

おすすめ

転載: blog.csdn.net/ecliujianbo/article/details/128449471