Funkcja withTimeout daje IllegalStateException: Nie ma pętli zdarzeń. Uruchom runBlocking {…}. w kliencie Kotlin Multiplatform iOS

13

Aktualizacja: Działa, jeśli najpierw wykonam koronę bez limitu czasu, a następnie za pomocą Timeout. Ale jeśli najpierw wykonam coroutine za pomocą Timeout, wówczas pojawi się błąd. to samo dotyczy Async.

Tworzę demo wieloplatformową aplikację kotlin, w której wykonuję wywołanie API za pomocą ktor. Chcę mieć konfigurowalną funkcję limitu czasu na żądanie ktor, więc używam withTimeout na poziomie coroutine.

Oto moje wywołanie funkcji z interfejsem API sieci.

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
}

Oto moja klasa AppDispatcher dla modułu 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()
        }
    }
}

}

więc funkcja limitu czasu daje mi następujący błąd w kliencie iOS.

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

Używam 1.3.2-native-mt-1 wersji kotlin-coroutine-native. Utworzyłem przykładową aplikację demonstracyjną pod następującym adresem URL. https://github.com/dudhatparesh/kotlin-multiplat-platform-example

Paresh Dudhat
źródło
Błąd pojawia się tylko w kliencie iOS? Klient Androida działa poprawnie?
Kushal,
Tak, klient Androida działa doskonale
Paresh Dudhat,
Pracuję nad podobnym problemem podczas próby aktualizacji github.com/joreilly/PeopleInSpace, aby używać natywnej wersji mout coroutines .... próbuję 1.3.3-native-mtwersji wymienionej w github.com/Kotlin/kotlinx.coroutines/issues/462 . Wydaje się, że powinniśmy używać, newSingleThreadContextale z jakiegoś powodu to nie rozwiązuje.
John O'Reilly,

Odpowiedzi:

5

Tak więc, jak wspomniano w powyższym komentarzu, miałem podobny problem, ale okazało się, że nie było to pobieranie native-mtwersji z powodu zależności przechodnich w innych bibliotekach. Dodano następujące i teraz rozwiązuje.

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

Zwróć także uwagę na wytyczne w https://github.com/Kotlin/kotlinx.coroutines/blob/native-mt/kotlin-native-sharing.md

Rozpoczęcie korzystania z tego w https://github.com/joreilly/PeopleInSpace

John O'Reilly
źródło
Właśnie tego spróbowałem. nie działało uzyskanie tego samego błędu.
Paresh Dudhat,
Dodałem twoją poprawkę do repozytorium na github.com/dudhatparesh/kotlin-multiplat-platform-example
Paresh Dudhat
Dzięki odpowiedzi Johna mogłem z powodzeniem wywołać poniższą funkcję z iOS `` @InternalCoroutinesApi fun backgroundTest () {val job = GlobalScope.launch {kprint („jesteśmy w głównym wątku \ n”) z Context (Dispatchers.Default) {kprint („hello \ n”) delay (2000) kprint („world \ n”)}}}} ``
Brendan Weinstein
Hej John. Dzięki za to. Masz jakiś pomysł, jak mogę zmusić Ktora do zbudowania? W jaki sposób mogę zmusić go do użycia 1.3.3-native-mt? RozumiemCould 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
1
@ JohnO'Reilly Jeszcze raz dziękuję. Rozwiązałem to, uaktualniając moją wersję stopniową do 6, tak jak w przykładzie.
Carson Holzheimer,
1

Jeśli chcesz używać [withTimeout]funkcji w korporacjach, musisz zmodyfikować swój interfejs, Dispatcheraby zaimplementować Delay. Oto przykład, w jaki sposób można to osiągnąć:

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

}

To rozwiązanie można łatwo zmodyfikować do własnych potrzeb.

Więcej informacji można znaleźć w tym wątku .

sztuka
źródło
Próbowałem również tego rozwiązania. wciąż daje ten sam błąd. jednak jeśli wykonam jakąkolwiek coroutine, która nie ma limitu czasu przed wykonaniem coroutine z limitem czasu, będzie działać dobrze.
Paresh Dudhat,
@PareshDudhat Wspomniane zachowanie jest raczej dziwne. Jest Dispatchers.Unconfineddyspozytor, który ma mechanizm podobny do opisywanego. Czy jesteś w pełni pewien sposobu, w jaki wypuszczasz swoją korupcję?
art.
Uruchamiam go za pomocą launch (dispatchers.main), próbowałem również uruchomić go za pomocą dispatcher.main + job, ale bez pomocy.
Wrzuciłem
0

Czasami aplikacja ios ma inne wymagania asynchroniczne w stosunku do aplikacji na Androida. Użyj tego kodu, aby rozwiązać problem z tymczasową wysyłką

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

Zobacz forum tego problemu: https://github.com/Kotlin/kotlinx.coroutines/issues/470

Antonio Yaphiar
źródło
Próbowałem tego, ale to też nie działa.
Paresh Dudhat,