withTimeout函数出现IllegalStateException错误:没有事件循环。在Kotlin多平台iOS客户端中使用runBlocking { ... }来启动一个事件循环。

19
更新: 如果我先执行一个没有超时的协程,然后再执行一个带有超时的协程,它就能工作。但如果我先执行一个带有超时的协程,那么就会出现错误。对于Async也是一样的。
我正在创建一个演示 Kotlin 多平台应用程序,在其中使用 ktor 执行 API 调用。 我希望在 ktor 请求上拥有可配置的超时函数,因此我在协程级别上使用了 withTimeout。
以下是我的网络 API 函数调用。
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
}

这是我为iOSMain模块编写的AppDispatcher类。
@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()
        }
    }
}

所以带有超时的函数会在iOS客户端中出现以下错误。

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

我正在使用 kotlin-coroutine-native 的 1.3.2-native-mt-1 版本。 我已经在以下网址创建了一个示例演示应用程序。 https://github.com/dudhatparesh/kotlin-multiplat-platform-example

错误只出现在 iOS 客户端吗?Android 客户端正常工作吗? - Kushal
是的,Android客户端运行得非常完美。 - Paresh Dudhat
当我尝试更新https://github.com/joreilly/PeopleInSpace以使用协程的本地mt版本时,遇到了类似的问题。 尝试使用https://github.com/Kotlin/kotlinx.coroutines/issues/462中提到的“1.3.3-native-mt”版本。似乎我们应该使用“newSingleThreadContext”,但因某种原因无法解决问题。 - John O'Reilly
3个回答

9

刚刚尝试了一下,还是出现了相同的错误。 - Paresh Dudhat
1
我已将您的修复添加到存储库 https://github.com/dudhatparesh/kotlin-multiplat-platform-example - Paresh Dudhat
由于John的回答,我成功地从iOS调用了下面的函数。@InternalCoroutinesApi fun backgroundTest() { val job = GlobalScope.launch { kprint("我们在主线程上 \n") withContext(Dispatchers.Default) { kprint("你好 \n") delay(2000) kprint("世界 \n") } } } - Brendan Weinstein
嘿,约翰。谢谢你的帮助。你有什么想法可以让ktor构建吗?我有没有办法强制使用“1.3.3-native-mt”?我得到了“无法解析org.jetbrains.kotlinx:kotlinx-coroutines-core-native:1.3.3。 要求: 项目:共享> io.ktor:ktor-client-ios:1.3.0> io.ktor:ktor-client-ios-iosx64:1.3.0” - Carson Holzheimer
@CarsonHolzheimer 我建议你查看 https://github.com/joreilly/PeopleInSpace 中的依赖设置。 - John O'Reilly
1
@JohnO'Reilly 再次感谢。我通过升级我的 Gradle 版本到像你在示例中一样的 6 版本来解决了这个问题。 - Carson Holzheimer

4

如果你想在协程中使用[withTimeout]函数,你需要修改你的Dispatcher来实现Delay接口。以下是一个实现这个目标的示例:

@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
    }

}

这个解决方案可以轻松地根据您的需求进行修改。

更多信息可以在此线程中找到。

更新

目前有一个kotlinx:kotlinx-coroutines-core工件的1.3.9-native-mt版本,它提供了在ios平台上使用Dispatchers.Main的能力(它直接支持delay)。它甚至支持用于后台工作的Dispatchers.Default。您可以在native-mt分支的文档中阅读。值得注意的是,ios的版本应该严格设置:

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

我也尝试了那个解决方案,但仍然出现同样的错误。但是,如果我执行任何没有超时的协程,在执行具有超时的协程之前,它会正常工作。 - Paresh Dudhat
@PareshDudhat 您提到的行为相当奇怪。有Dispatchers.Unconfined调度程序,其机制与您所描述的相似。您确定您启动协程的方式是正确的吗? - art
我正在使用 launch(dispatchers.main) 进行启动,我也尝试过使用 dispatcher.main + job 进行启动,但没有帮助。我已经将最新的提交推送到 GitHub 存储库中。 - Paresh Dudhat
太棒了,我想调用延迟函数在挂起函数中,这个解决方案非常好用!感谢@art。 - TheYogi

0
有时候iOS应用程序与Android应用程序具有不同的异步要求。 使用此代码暂时解决调度问题。
object MainLoopDispatcher: CoroutineDispatcher() {
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        NSRunLoop.mainRunLoop().performBlock {
            block.run()
        }
    }
}

请查看论坛以了解此问题:https://github.com/Kotlin/kotlinx.coroutines/issues/470

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接