web-dev-qa-db-ja.com

withTimeout関数でIllegalStateExceptionが発生する:イベントループはありません。 runBlocking {...}を使用して開始します。 Kotlin Multiplatform iOSクライアント

更新:最初にタイムアウトなしでコルーチンを実行し、次にwithTimeoutを実行すると機能します。しかし、最初にコルーチンwithTimeoutを実行すると、エラーが発生します。同じことが非同期にも当てはまります。

私は、ktorを使用してAPI呼び出しを実行するデモkotlinマルチプラットフォームアプリケーションを作成しています。コルーチンレベルでwithTimeoutを使用しているので、ktorリクエストに構成可能なタイムアウト関数を設定したいと思います。

ネットワーク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

12
Paresh Dudhat

コルーチンで[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
art

場合によってはiOSアプリにAndroid appとの異なる非同期要件があります。一時的なディスパッチの問題にはこのコードを使用してください

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

この問題のフォーラムを参照してください: https://github.com/Kotlin/kotlinx.coroutines/issues/47

0
antonio yaphiar