【问题标题】:withTimeout function gives IllegalStateException: There is no event loop. Use runBlocking { ... } to start one. in Kotlin Multiplatform iOS clientwithTimeout 函数给出 IllegalStateException: 没有事件循环。使用 runBlocking { ... } 启动一个。在 Kotlin 多平台 iOS 客户端中
【发布时间】:2020-04-09 18:01:44
【问题描述】:

更新: 如果我先执行一个没有超时的协程,然后再执行一个超时,它就可以工作。但是如果我先执行一个带有Timeout的协程,那么它会给我一个错误。异步也是如此。

我正在创建一个演示 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 版本。 我在以下 URL 创建了一个示例演示应用程序。 https://github.com/dudhatparesh/kotlin-multiplat-platform-example

【问题讨论】:

标签: ios kotlin kotlin-coroutines ktor kotlin-native


【解决方案1】:

所以,正如上面评论中提到的,我遇到了类似的问题,但由于其他库中的传递依赖关系,它没有选择 native-mt 版本。添加了以下内容,现在正在解决。

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

另请注意https://github.com/Kotlin/kotlinx.coroutines/blob/native-mt/kotlin-native-sharing.md中的指导

https://github.com/joreilly/PeopleInSpace开始使用这个

【讨论】:

  • 刚刚试过。没有工作得到同样的错误。
  • 我已在 github.com/dudhatparesh/kotlin-multiplat-platform-example 的存储库中添加了您的修复程序
  • 感谢 John 的回答,我能够从 iOS 成功调用以下函数 `` @InternalCoroutinesApi fun backgroundTest() { val job = GlobalScope.launch { kprint("我们在主线程 \n ") withContext(Dispatchers.Default) { kprint("hello \n") delay(2000) kprint("world \n") } } } ```
  • 嘿约翰。谢谢你。知道如何让 ktor 构建吗?有什么办法可以强制它使用1.3.3-native-mt?我得到Could not resolve org.jetbrains.kotlinx:kotlinx-coroutines-core-native:1.3.3. Required by: project :shared &gt; io.ktor:ktor-client-ios:1.3.0 &gt; io.ktor:ktor-client-ios-iosx64:1.3.0
  • @JohnO'Reilly 再次感谢。我通过像示例中那样将我的 gradle 版本升级到 6 来解决它。
【解决方案2】:

如果你想在协程中使用[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
    }

}

可以根据您的需要轻松修改此解决方案。

更多信息可以在this thread找到。

更新

目前有一个版本1.3.9-native-mtkotlinx:kotlinx-coroutines-core 工件可以在ios 平台上使用Dispatchers.Main(它支持开箱即用的delay)。它甚至支持用于后台工作的Dispatchers.Default。您可以在 native-mt 分支中阅读 docs。需要注意的是,ios的版本要严格设置:

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

【讨论】:

  • 我也尝试了该解决方案。仍然,它给出了同样的错误。但是,如果我在执行超时协程之前执行任何没有超时的协程,它就可以正常工作。
  • @PareshDudhat 你提到的行为很奇怪。有 Dispatchers.Unconfined 调度程序,它的 mechanism 与您所描述的非常相似。你完全确定你启动协程的方式吗?
  • 我正在使用 launch(dispatchers.main) 启动它,我也尝试使用 dispatcher.main + job 启动它,但没有帮助。我在 GitHub repo 上推送了最新的提交
  • 漂亮,我想要的只是在挂起函数中调用延迟,这个解决方案就像一个魅力!谢谢@艺术
【解决方案3】:

有时 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

【讨论】:

    猜你喜欢
    • 2021-06-29
    • 2021-07-17
    • 1970-01-01
    • 1970-01-01
    • 2014-07-27
    • 2010-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多