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


13

更新:最初にタイムアウトなしでコルーチンを実行し、次に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


エラーはiOSクライアントでのみ発生しますか?Androidクライアントは正常に動作しますか?
Kushal

はい、Androidクライアントは完全に問題なく動作しています
Paresh Dudhat、

github.com/joreilly/PeopleInSpaceを更新して、ネイティブmtバージョンのコルーチンを使用しようとすると、同様の問題1.3.3-native-mt発生します。使用newSingleThreadContextしているようですが、何らかの理由で解決しません。
John O'Reilly

回答:


5

したがって、上記のコメントで述べたように、私は同様の問題を抱えていましたが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でこれを利用し始めます


試してみただけです。同じエラーが発生して動作しませんでした。
Paresh Dudhat


Johnの回答のおかげで、以下の関数を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

やあジョン。これをありがとう。私にktorをビルドさせる方法はありますか?強制的に使用する方法はあり1.3.3-native-mtますか?Could 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
わかり

1
@ JohnO'Reillyありがとうございます。私のgradleバージョンを例のように6にアップグレードすることで解決しました。
カーソンホルツハイマー

1

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

}

このソリューションは、ニーズに合わせて簡単に変更できます。

詳細については、このスレッドをご覧ください。


私もその解決策を試しました。それでも、同じエラーが発生します。ただし、タイムアウト付きのコルーチンを実行する前にタイムアウトのないコルーチンを実行すると、問題なく機能します。
Paresh Dudhat

@PareshDudhatこれまでに述べた動作はかなり奇妙です。あなたが説明しているものにかなり似Dispatchers.Unconfinedメカニズムを持つディスパッチャーがあります。コルーチンを起動する方法について完全に確信していますか?
アート

私はlaunch(dispatchers.main)で起動していますが、dispatcher.main + jobでも起動しようとしましたが、助けはありません。最新のコミットをGitHubリポジトリにプッシュしました
Paresh Dudhat

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


私はそれを試しましたが、それもうまくいきません。
Paresh Dudhat
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.