Siempre compruebo la implementación de las cosas que uso.
Actualmente estoy usando una biblioteca de inyección que no admite funciones suspensibles (Koin), por lo que, solo (incluso si no lo recomiendo) para arrancar la aplicación, estoy usando runBlocking algunas veces.
Para tener registros más completos, estoy enriqueciendo el contexto coroutine con algo de información, pero esa información se pierde en la mayoría de los cambios de contexto ( launch , async y runBlocking , entre otros).
Especialmente, dado el hecho de que los métodos sin suspensión no tienen acceso a CoroutineContext, tengo mucha curiosidad de dónde lo obtiene runBlocking .
Puedes usar runBlocking como:
runBlocking {...} Sin embargo, cuando verifico su implementación, tiene dos parámetros: un CoroutineContext y el bloque de suspensión que se ejecutará. Ninguno de esos parámetros tiene un valor predeterminado, entonces, ¿por qué puedo llamarlo sin pasarlo? ¡Realmente no lo entiendo!
Además, la página dice que el valor predeterminado es EmptyCoroutineContext pero los documentos del código dicen algo sobre un bucle de eventos.
Entonces vuelvo a preguntar? ¿Por qué puedo llamarlo sin pasar un valor y cuál es el valor predeterminado real?
Por defecto runBlocking() comienza con un contexto de rutina vacío.
El hecho de que el context no tenga un valor predeterminado es realmente confuso y extraño. Creo (pero no estoy 100% seguro) que esto se debe a que al hacer ctrl+clic en runBlocking() vamos a la implementación, por lo que es una definición actual . Pero el código se compila contra la declaración de expect . No encontré una manera fácil de ver la declaración expect directamente en IntelliJ, pero se puede encontrar aquí :
public expect fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T Como podemos ver, el context en esta declaración tiene un valor predeterminado. Aún así, esto es realmente confuso cuando se usa IntelliJ.
Y con respecto al ciclo de eventos mencionado: sí, runBlocking() crea (o reutiliza) un ciclo de eventos, pero no veo cómo se relaciona con el contexto coroutine.
Recuerde que el contexto que pasa a un generador de rutinas como runBlocking o launch no es el contexto que realmente estará disponible dentro de la rutina, sino su padre . El constructor agrega sus propios elementos y los fusiona en el contexto que proporcionó.
runBlocking usa su propio despachador, que existe solo durante el tiempo de vida de la llamada a la función runBlocking . Puede encontrar este despachador en el contexto coroutine disponible dentro del cuerpo de runBlocking , por ejemplo, usando este código:
import kotlinx.coroutines.runBlocking fun main() { runBlocking { val ctxElems = coroutineContext.fold(mutableListOf<Pair<Any, Any>>()) { list, element -> list.also { it.add(element.key to element) } } for ((key, value) in ctxElems) { println("${key::class.qualifiedName}: $value") } } }esto imprime
kotlinx.coroutines.CoroutineId.Key: CoroutineId(1) kotlinx.coroutines.Job.Key: "coroutine#1":BlockingCoroutine{Active}@12843fce kotlin.coroutines.ContinuationInterceptor.Key: BlockingEventLoop@3dd3bcd(los despachadores de rutina pertenecen a la categoría más amplia de interceptores de continuación)
La otra parte de su pregunta, por qué no tiene que pasar el parámetro aparentemente no predeterminado, se responde en otra parte. Básicamente, es un artefacto del IDE y las declaraciones expected frente a actual . La declaración relevante es la expected , y actual es un detalle de implementación, pero el IDE lo lleva a esa.