Leí que el uso de Globalscope está muy desaconsejado aquí .
Tengo un caso de uso simple. Para cada mensaje kafka (digamos una lista de ID) que recibo, tengo que dividirlo e invocar un servicio de descanso simultáneamente para cada uno de esos ID y esperar a que se complete y continuar con otras tareas sincrónicas. No hay nada más en esa aplicación que requiera rutina. En este caso, ¿puedo salirme con la mía usando Globalscope ?
Nota: Esta no es una aplicación para Android. Es un procesador de flujo kafka que se ejecuta en el lado del servidor. Es una aplicación efímera, sin estado y en contenedores (Docker) que se ejecuta en Kubernetes (cumple con Buzzword, por así decirlo)
Debe definir el alcance de su simultaneidad de forma adecuada mediante la simultaneidad estructurada. Sus rutinas pueden filtrarse si no hace esto. En su caso, parece apropiado limitarlos al procesamiento de un solo mensaje.
Aquí hay un ejemplo:
/* I don't know Kafka, but let's pretend this function gets * called when you receive a new message */ suspend fun onMessage(msg: Message) { val ids: List<Int> = msg.getIds() val jobs = ids.map { id -> GlobalScope.launch { restService.post(id) } } jobs.joinAll() } Si una de las llamadas a restService.post(id) falla con una excepción, el ejemplo volverá a generar la excepción de inmediato y todos los trabajos que aún no se hayan completado se perderán. Continuarán ejecutándose (potencialmente indefinidamente), y si fallan, no lo sabrá.
Para resolver esto, necesita definir el alcance de sus rutinas. Aquí está el mismo ejemplo sin la fuga:
suspend fun onMessage(msg: Message) = coroutineScope { val ids: List<Int> = msg.getIds() ids.forEach { id -> // launch is called on "this", which is the coroutineScope. launch { restService.post(id) } } } En este caso, si una de las llamadas a restService.post(id) falla, todas las demás corrutinas no completadas dentro del alcance de la corrutina se cancelarán. Cuando deje el alcance, puede estar seguro de que no ha filtrado ninguna corrutina.
Además, debido a que coroutineScope esperará hasta que se completen todas las corrutinas secundarias, puede descartar la llamada a jobs.joinAll() .
Nota al margen: una convención al escribir una función que inicia algunas corrutinas es permitir que la persona que llama decida el alcance de la corrutina usando el parámetro del receptor. Hacer esto con la función onMessage podría verse así:
fun CoroutineScope.onMessage(msg: Message): List<Job> { val ids: List<Int> = msg.getIds() return ids.map { id -> // launch is called on "this", which is the coroutineScope. launch { restService.post(id) } } }Según los documentos , se desaconseja encarecidamente el uso de async o el lanzamiento en la instancia de GlobalScope , el código de la aplicación generalmente debe usar CoroutineScope definido por la aplicación .
Si nos fijamos en la definición de GlobalScope veremos que se declara como objeto :
object GlobalScope : CoroutineScope { ... } Un objeto representa una única instancia estática (Singleton) . En Kotlin/JVM, una variable estática surge cuando la JVM carga una clase y muere cuando la clase se descarga. Cuando utilice GlobalScope por primera vez, se cargará en la memoria y permanecerá allí hasta que suceda uno de los siguientes:
Por lo tanto, consumirá algo de memoria mientras se ejecuta la aplicación del servidor. Incluso si su aplicación de servidor ha terminado de ejecutarse pero el proceso no se destruye, una corrutina iniciada aún puede estar ejecutándose y consumir la memoria.
Iniciar una nueva corrutina desde el ámbito global mediante GlobalScope.async o GlobalScope.launch creará una corrutina " independiente " de nivel superior.
El mecanismo que proporciona la estructura de las rutinas se denomina concurrencia estructurada . Veamos qué beneficios tiene la concurrencia estructurada sobre los alcances globales :
- El alcance generalmente es responsable de las corrutinas secundarias, y su vida útil se adjunta a la vida útil del alcance.
- El alcance puede cancelar automáticamente las corrutinas secundarias si algo sale mal o si un usuario simplemente cambia de opinión y decide revocar la operación.
- El ámbito espera automáticamente a que se completen todas las corrutinas secundarias. Por lo tanto, si el alcance corresponde a una corrutina, entonces la corrutina principal no se completa hasta que se completen todas las corrutinas lanzadas en su alcance.
Cuando se usa GlobalScope.async , no hay una estructura que vincule varias corrutinas a un alcance más pequeño. Las corrutinas iniciadas desde el alcance global son todas independientes ; su tiempo de vida está limitado solo por el tiempo de vida de toda la aplicación. Es posible almacenar una referencia a la corrutina iniciada desde el alcance global y esperar a que finalice o cancelarla explícitamente, pero no sucederá automáticamente como lo haría con una estructurada . Si queremos cancelar todas las corrutinas en el alcance, con concurrencia estructurada , solo necesitamos cancelar la corrutina principal y esto automáticamente propaga la cancelación a todas las corrutinas secundarias.
Si no necesita aplicar el alcance de una corrutina a un objeto de vigencia específico y desea iniciar una corrutina independiente de nivel superior que opere durante toda la vigencia de la aplicación y no se cancele prematuramente y no desea utilizar los beneficios de la concurrencia estructurada , luego siga adelante y use alcances globales .
En tu enlace dice:
El código de la aplicación generalmente debe usar
CoroutineScopedefinido por la aplicación; se desaconseja enfáticamente usarasyncolaunchen la instancia deGlobalScope.
Mi respuesta aborda esto.
En términos generales, GlobalScope puede ser una mala idea, porque no está vinculado a ningún trabajo. Debes usarlo para lo siguiente:
El alcance global se utiliza para lanzar corrutinas de nivel superior que funcionan durante toda la vida útil de la aplicación y no se cancelan prematuramente.
Lo cual no parece ser su caso de uso.
Para obtener más información, hay un pasaje en los documentos oficiales en https://kotlinlang.org/docs/reference/coroutines/basics.html#structured-concurrency
Todavía hay algo que desear para el uso práctico de las rutinas. Cuando usamos
GlobalScope.launch, creamos una rutina de nivel superior. Aunque es liviano, aún consume algunos recursos de memoria mientras se ejecuta. Si olvidamos mantener una referencia a la rutina recién lanzada, aún se ejecuta. ¿Qué pasa si el código en la corrutina se bloquea (por ejemplo, retrasamos demasiado por error), qué sucede si lanzamos demasiadas corrutinas y nos quedamos sin memoria? Tener que mantener manualmente una referencia a todas las corrutinas lanzadas y unirlas es propenso a errores.Hay una solución mejor. Podemos usar concurrencia estructurada en nuestro código. En lugar de lanzar corrutinas en
GlobalScope, como solemos hacer con los hilos (los hilos siempre son globales), podemos lanzar corrutinas en el ámbito específico de la operación que estamos realizando.En nuestro ejemplo, tenemos una función principal que se convierte en una corrutina utilizando el generador de
runBlocking. Cada constructor de rutinas, incluidorunBlocking, agrega una instancia deCoroutineScopeal alcance de su bloque de código. Podemos lanzar corrutinas en este alcance sin tener que unirlas explícitamente, porque una corrutina externa (runBlockingen nuestro ejemplo) no se completa hasta que se completan todas las corrutinas lanzadas en su alcance. Por lo tanto, podemos simplificar nuestro ejemplo:import kotlinx.coroutines.* fun main() = runBlocking { // this: CoroutineScope launch { // launch new coroutine in the scope of runBlocking delay(1000L) println("World!") } println("Hello,") }
Entonces, en esencia, se desaconseja porque lo obliga a mantener referencias y usar join , lo que se puede evitar con la concurrencia estructurada. (Consulte el ejemplo de código anterior). El artículo cubre muchas de las sutilezas.