更新:最初にタイムアウトなしでコルーチンを実行し、次に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
コルーチンで[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 を参照してください。
場合によっては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