La función withTimeout proporciona IllegalStateException: no hay ningún bucle de eventos. Use runBlocking {...} para comenzar uno. en el cliente Kotlin Multiplatform iOS

13

Actualización: funciona si primero ejecuto una rutina sin tiempo de espera y luego con Timeout. Pero si ejecuto primero una rutina con Timeout, entonces me da un error. Lo mismo ocurre con Async también.

Estoy creando una aplicación de demostración multiplataforma kotlin donde estoy ejecutando una llamada API con ktor. Quiero tener una función de tiempo de espera configurable a petición de ktor, así que estoy usando withTimeout a nivel de rutina.

Aquí está mi llamada de función con API de red.

suspend fun <T> onNetworkWithTimeOut(
    url: String,
    timeoutInMillis: Long,
    block: suspend CoroutineScope.() -> Any): T {
    return withTimeout(timeoutInMillis) {
        withContext(dispatchers.io, block)
    } as T
}

suspend fun <T> onNetworkWithoutTimeOut(url: String, block: suspend CoroutineScope.() -> Any): T {
    return withContext(dispatchers.io, block) as T
}

Aquí está mi clase AppDispatcher para el módulo iOSMain.

@InternalCoroutinesApi
actual class AppDispatchersImpl : AppDispatchers {
@SharedImmutable
override val main: CoroutineDispatcher =
    NsQueueDispatcher(dispatch_get_main_queue())

@SharedImmutable
override val io: CoroutineDispatcher =
    NsQueueDispatcher(dispatch_get_main_queue())

internal class NsQueueDispatcher(
    @SharedImmutable private val dispatchQueue: dispatch_queue_t
) : CoroutineDispatcher() {
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        NSRunLoop.mainRunLoop().performBlock {
            block.run()
        }
    }
}

}

entonces la función con el tiempo de espera me da el siguiente error en el cliente iOS.

kotlin.IllegalStateException: There is no event loop. Use runBlocking { ... } to start one.

Estoy usando la versión 1.3.2-native-mt-1 de kotlin-coroutine-native. He creado una aplicación de demostración de muestra en la siguiente URL. https://github.com/dudhatparesh/kotlin-multiplat-platform-example

Paresh Dudhat
fuente
¿El error solo viene en el cliente iOS? ¿El cliente de Android funciona correctamente?
Kushal
Sí, el cliente de Android funciona perfectamente bien
Paresh Dudhat
Me encuentro con un problema similar al intentar actualizar github.com/joreilly/PeopleInSpace para utilizar la versión mt nativa de las corutinas ... versión de prueba 1.3.3-native-mtmencionada en github.com/Kotlin/kotlinx.coroutines/issues/462 . Parece que deberíamos estar usando, newSingleThreadContextpero eso no se resuelve por alguna razón.
John O'Reilly

Respuestas:

5

Entonces, como se mencionó en el comentario anterior, tuve un problema similar, pero resultó que no estaba recogiendo la native-mtversión debido a dependencias transitivas en otras bibliotecas. Se agregó el siguiente y se está resolviendo ahora.

        implementation('org.jetbrains.kotlinx:kotlinx-coroutines-core-native') 
        {
            version {
                strictly '1.3.3-native-mt'
            }
        }

También tenga en cuenta la orientación en https://github.com/Kotlin/kotlinx.coroutines/blob/native-mt/kotlin-native-sharing.md

Comenzando a hacer uso de esto en https://github.com/joreilly/PeopleInSpace

John O'Reilly
fuente
Solo intenté eso. no funcionó obteniendo el mismo error.
Paresh Dudhat
He agregado su corrección en el repositorio en github.com/dudhatparesh/kotlin-multiplat-platform-example
Paresh Dudhat
Gracias a la respuesta de John, pude llamar a la siguiente función con éxito desde iOS `` `@ @ InternalCoroutinesApi fun backgroundTest () {val job = GlobalScope.launch {kprint (" we are on main thread \ n ") withContext (Dispatchers.Default) {kprint ("hello \ n") delay (2000) kprint ("world \ n")}}} `` `
Brendan Weinstein
Hola John. Gracias por esto. ¿Alguna idea de cómo puedo hacer que ktor construya entonces? ¿De alguna manera puedo forzarlo a usar 1.3.3-native-mt? Me saleCould not resolve org.jetbrains.kotlinx:kotlinx-coroutines-core-native:1.3.3. Required by: project :shared > io.ktor:ktor-client-ios:1.3.0 > io.ktor:ktor-client-ios-iosx64:1.3.0
Carson Holzheimer
1
@ JohnO'Reilly Gracias de nuevo. Lo resolví actualizando mi versión de Gradle a 6 como lo había hecho en el ejemplo.
Carson Holzheimer
1

Si desea utilizar [withTimeout]funciones en las rutinas, debe modificar su interfaz Dispatcherpara implementar Delay. Aquí hay un ejemplo de cómo se puede lograr esto:

@UseExperimental(InternalCoroutinesApi::class)
class UI : CoroutineDispatcher(), Delay {

    override fun dispatch(context: CoroutineContext, block: Runnable) {
        dispatch_async(dispatch_get_main_queue()) {
            try {
                block.run()
            } catch (err: Throwable) {
                throw err
            }
        }
    }

    @InternalCoroutinesApi
    override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeMillis * 1_000_000), dispatch_get_main_queue()) {
            try {
                with(continuation) {
                    resumeUndispatched(Unit)
                }
            } catch (err: Throwable) {
                throw err
            }
        }
    }

    @InternalCoroutinesApi
    override fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle {
        val handle = object : DisposableHandle {
             var disposed = false
                private set

            override fun dispose() {
                disposed = true
            }
        }
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeMillis * 1_000_000), dispatch_get_main_queue()) {
            try {
                if (!handle.disposed) {
                    block.run()
                }
            } catch (err: Throwable) {
                throw err
            }
        }

        return handle
    }

}

Esta solución se puede modificar fácilmente para sus necesidades.

Se puede encontrar más información en este hilo .

Arte
fuente
Intenté esa solución también. Aún así, está dando el mismo error. sin embargo, si ejecuto cualquier rutina que no tenga tiempo de espera antes de ejecutarla con tiempo de espera, funciona bien.
Paresh Dudhat
@PareshDudhat El comportamiento que has mencionado es bastante extraño. Hay Dispatchers.Unconfineddespachador, que tiene el mecanismo bastante similar a lo que está describiendo. ¿Estás completamente seguro de la forma en que lanzas tu corutina?
arte
Lo estoy iniciando con launch (dispatchers.main), también he intentado iniciarlo con dispatcher.main + job pero no hay ayuda.
Presioné
0

A veces, la aplicación ios tiene un requisito asíncrono diferente con una aplicación de Android. Use este código para un problema de despacho temporal

object MainLoopDispatcher: CoroutineDispatcher() {
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        NSRunLoop.mainRunLoop().performBlock {
            block.run()
        }
    }
}

Consulte el foro para este problema: https://github.com/Kotlin/kotlinx.coroutines/issues/470

antonio yaphiar
fuente
Lo he intentado pero no funciona tan bien.
Paresh Dudhat