A função withTimeout fornece IllegalStateException: Não há loop de eventos. Use runBlocking {…} para iniciar um. no cliente iOS Kotlin Multiplatform

13

Atualização: Funciona se eu executar primeiro uma corotina sem tempo limite e depois com Timeout. Porém, se eu executar uma corotina com Timeout primeiro, isso me dará um erro. O mesmo vale para o Async também.

Estou criando um aplicativo multiplataforma de demonstração kotlin em que estou executando uma chamada de API com o ktor. Eu quero ter uma função de tempo limite configurável na solicitação do ktor, portanto, estou usando withTimeout no nível da corotina.

Aqui está minha chamada de função com a API da rede.

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
}

Aqui está minha classe AppDispatcher para o 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()
        }
    }
}

}

então a função com o tempo limite me fornece o seguinte erro no cliente iOS.

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

Estou usando a versão 1.3.2-native-mt-1 do kotlin-coroutine-native. Eu criei um aplicativo de demonstração de exemplo no seguinte URL. https://github.com/dudhatparesh/kotlin-multiplat-platform-example

Paresh Dudhat
fonte
O erro está chegando apenas no cliente iOS? Cliente Android funciona corretamente?
Kushal
Sim cliente Android está funcionando perfeitamente bem
Paresh Dudhat
Estou enfrentando um problema semelhante ao tentar atualizar o github.com/joreilly/PeopleInSpace para usar a versão mt nativa de coroutines .... 1.3.3-native-mtversão de teste mencionada em github.com/Kotlin/kotlinx.coroutines/issues/462 . Parece que deveríamos estar usando, newSingleThreadContextmas isso não resolve por algum motivo.
John O'Reilly

Respostas:

5

Portanto, como mencionado no comentário acima, tive um problema semelhante, mas descobri que não estava pegando a native-mtversão devido a dependências transitivas em outras bibliotecas. Adicionado a seguir e está resolvendo agora.

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

Observe também as orientações em https://github.com/Kotlin/kotlinx.coroutines/blob/native-mt/kotlin-native-sharing.md

Começando a usar isso em https://github.com/joreilly/PeopleInSpace

John O'Reilly
fonte
Apenas tentei isso. não funcionou com o mesmo erro.
Paresh Dudhat
Eu adicionei a sua correção sobre o repositório em github.com/dudhatparesh/kotlin-multiplat-platform-example
Paresh Dudhat
Graças à resposta de John, consegui chamar a função abaixo com sucesso no iOS `` `@InternalCoroutinesApi fun backgroundTest () {val job = GlobalScope.launch {kprint (" estamos no segmento principal \ n ") com withContext (Dispatchers.Default) {kprint ("hello \ n") delay (2000) kprint ("world \ n")}}} `` `
Brendan Weinstein
Hey John. Obrigado por isso. Alguma idéia de como eu posso fazer o ktor construir então? De alguma forma eu posso forçá-lo a usar 1.3.3-native-mt? Eu receboCould 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 24/01
11
@ JohnO'Reilly Obrigado novamente. Eu o resolvi atualizando minha versão gradle para 6, como você fez no exemplo.
Carson Holzheimer 25/01
1

Se você quiser usar [withTimeout]funções em corotinas, precisará modificar sua interface Dispatcherpara implementar Delay. Aqui está um exemplo de como isso pode ser alcançado:

@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 solução pode ser facilmente modificada para suas necessidades.

Mais informações podem ser encontradas neste tópico .

arte
fonte
Eu tentei essa solução também. ainda assim, está dando o mesmo erro. no entanto, se eu executar qualquer corotina que não tenha tempo limite antes de executá-la com tempo limite, ela funcionará perfeitamente.
Paresh Dudhat
@PareshDudhat O comportamento que você mencionou é bastante estranho. Há Dispatchers.Unconfineddespachante, que tem o mecanismo bastante semelhante ao que você está descrevendo. Você tem certeza absoluta da maneira como lança sua rotina?
Art
Estou iniciando com launch (dispatchers.main), também tentei lançá-lo com dispatcher.main + job, mas sem ajuda. Eu empurrei o commit mais recente no
repositório
0

Às vezes, o aplicativo ios tem um requisito assíncrono diferente com um aplicativo Android. Use este código para problema de despacho temporário

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

Consulte o fórum para esse problema: https://github.com/Kotlin/kotlinx.coroutines/issues/470

antonio yaphiar
fonte
Eu tentei isso, mas não está funcionando tão bem.
Paresh Dudhat