El flujo del Estado de coroutina deja de emitir cuando se cancela el alcance de coroutina

Un registro de mis problemas

tengo un program a colaborativo StateFlow que se comparte entre las partes de mi aplicación. Cuando I cancel es CoroutineScope del colector aguas abajo, JobCancellationException se propaga hacia arriba a StateFlow y deja de enviar valores para todos los colectores actuales y futuros.StateFlow:
val songsRelay: Flow<List<Song>> by lazy {
    MutableStateFlow<List<Song>?>(null).apply {
        CoroutineScope(Dispatchers.IO)
            .launch { songDataDao.getAll().distinctUntilChanged().collect { value = it } }
    }.filterNotNull()
}
El típico "Presentador"en mi código implementa las siguientes clases base:
abstract class BasePresenter<T : Any> : BaseContract.Presenter<T> {

    var view: T? = null

    private val job by lazy {
        Job()
    }

    private val coroutineScope by lazy { CoroutineScope( job + Dispatchers.Main) }

    override fun bindView(view: T) {
        this.view = view
    }

    override fun unbindView() {
        job.cancel()
        view = null
    }

    fun launch(block: suspend CoroutineScope.() -> Unit): Job {
        return coroutineScope.launch(block = block)
    }
}
La implementación de BasePresenter puede llamar a launch{ songsRelay.collect {...} }Cuando el presentador se deshace, cancelé el trabajo padre para evitar fugas. Cada vez que el presentador que recoge songsRelay StateFlow se desbloquea, StateFlow termina básicamente con JobCancellationException, y ningún otro colector/presentador puede recoger valores de él.
Observo que puedo llamar a job.cancelChildren() en su lugar, lo que parece factible (StateFlow no incluye JobCancellationException). Sin embargo, si no puedo cancelar el trabajo en sí, me pregunto cuál es el punto de declarar el padre job. Puedo borrar completamente job y luego llamar a coroutineScope.coroutineContext.cancelChildren() para lograr el mismo efecto.
¿Es suficiente si llamo a job.cancelChildren()? Siento que si no llamo a coroutineScope.cancel() o job.cancel(), puede que no haya limpiado correctamente o completamente la tarea que he comenzado.
Tampoco entiendo por qué JobCancellationException se propaga hacia arriba cuando se llama job.cancel(). ¿job no es un "padre"aquí? ¿Por qué cancelarlo afecta a mi StateFlow? Actualización

Las soluciones eficaces son las siguientes:

:
¿Está seguro de que todos los demostradores songRelay serán cancelados? Ejecuté la prueba e Imprimí "Song Relay completed"porque onCompletion también capturó la excepción aguas abajo. Sin embargo, después de que el relé de la canción imprima "hecho", el presentador 2 emite un valor de 2. Si cancelé el presentador 2, imprimiré "Song Relay completed"de nuevo y mostraré la excepción de cancelación de trabajo para el trabajo del presentador 2.
Realmente Encuentro interesante cómo una instancia de flujo envía un mensaje para cada colector. No me di cuenta.
    val songsRelay: Flow<Int> by lazy {
        MutableStateFlow<Int?>(null).apply {
            CoroutineScope(Dispatchers.IO)
                    .launch {
                        flow {
                            emit(1)
                            delay(1000)
                            emit(2)
                            delay(1000)
                            emit(3)
                        }.onCompletion {
                            println("Dao completed")
                        }.collect { value = it }
                    }
        }.filterNotNull()
                .onCompletion { cause ->
                    println("Song relay completed: $cause")
                }
    }

    @Test
    fun test() = runBlocking {
        val job = Job()
        val presenterScope1 = CoroutineScope(job + Dispatchers.Unconfined)
        val presenterScope2 = CoroutineScope(Job() + Dispatchers.Unconfined)

        presenterScope1.launch {
            songsRelay.onCompletion { cause ->
                println("Presenter 1 Completed: $cause")
            }.collect {
                println("Presenter 1 emits: $it")
            }
        }

        presenterScope2.launch {
            songsRelay.collect {
                println("Presenter 2 emits: $it")
            }
        }

        presenterScope1.cancel()

        delay(2000)
        println("Done test")
    }
Creo que necesitas usar SupervisorJob en el presentador base en lugar de Job. En general, el uso de Job es un error para todo el presentador, ya que una colaboración fallida cancelará todas las colaboraciones en el presentador. Normalmente no es lo que quieres.