Sobes.tech
Middle

Jak można przełączać wątki w Flow? Na którym wątku działa Flow?

sobes.tech AI

Odpowiedź od AI

Domyślnie Flow działa na tym wątku, z którego został uruchomiony. Możesz zmienić wątek za pomocą operatora flowOn.

import kotlinx.coroutines.CoroutineDispatcher // Dla CoroutineDispatcher
import kotlinx.coroutines.Dispatchers // Dla Dispatchers
import kotlinx.coroutines.flow.Flow // Dla Flow
import kotlinx.coroutines.flow.flow // Dla flow
import kotlinx.coroutines.flow.flowOn // Dla flowOn
import kotlinx.coroutines.runBlocking // Dla runBlocking

// Przykład funkcji zwracającej Flow
fun simpleFlow(): Flow<Int> = flow {
    logThread("Flow rozpoczęty") // Logowanie wątku, na którym uruchomiono builder flow
    for (i in 1..3) {
        emit(i) // Emitujemy wartości
    }
}

fun main() = runBlocking {
    simpleFlow()
        .flowOn(Dispatchers.IO) // Zmiana upstream (emiterów) na Dispatchers.IO
        .collect { value ->
            logThread("Zebrano $value") // Logowanie wątku, na którym zbierane są wartości
        }
}

// Funkcja pomocnicza do logowania wątku
fun logThread(msg: String) {
    println("[$msg] [${Thread.currentThread().name}]")
}

Operator flowOn wpływa na wątek, na którym wykonywane są operatory przed nim w łańcuchu (w tym builder flow). Operatory po flowOn są wykonywane na wątku określonym w argumencie flowOn. Jeśli w łańcuchu jest kilka flowOn, każdy z nich wpływa na część łańcucha przed nim.

Przykład z wieloma flowOn:

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.runBlocking

fun simpleFlowWithMultipleFlowOn(): Flow<String> = flow {
    logThread("Builder flow rozpoczęty") // Będzie działać na pierwszym określonym flowOn
    emit("A")
    emit("B")
}.map {
    logThread("Mapowanie $it") // Będzie działać na drugim określonym flowOn
    it.toLowerCase()
}.flowOn(Dispatchers.Default) // Drugi flowOn wpływa na map() i builder flow
.filter {
    logThread("Filtrowanie $it") // Będzie działać na wątku, z którego wywoływane jest collect (domyślnie main/runBlocking)
    true
}.flowOn(Dispatchers.IO) // Pierwszy flowOn wpływa na map() i builder flow

fun main() = runBlocking {
    simpleFlowWithMultipleFlowOn().collect { value ->
        logThread("Zbieranie $value") // Działa na wątku runBlocking (main)
    }
}

fun logThread(msg: String) {
    println("[$msg] [${Thread.currentThread().name}]")
}

W tym przykładzie:

  • flow builder i map będą działać na Dispatchers.Default (ponieważ to ostatni flowOn w łańcuchu przed nimi).
  • filter będzie działać na wątku, z którego wywołuje się collect, czyli na wątku, na którym wykonuje się runBlocking (zwykle Main w Android).

Ważne jest, aby zrozumieć, że flowOn tworzy bufor pośredni i nie jest "przezroczystą" zmianą wątku.