Closed pkliang closed 2 years ago
We will have it with #462 Current default dispatcher was chosen as the simplest MVP and it's not something we want to stick with. Without #462 it's too error-prone to introduce "special" dispatcher for iOS, it will be constant source of confusing bugs in current API.
In my case, the current default dispatcher is not too much of a concern, because asynchronous operations are handled by other native libraries with callbacks on the main thread, and there are no intense computations inside coroutines, so it's fine for all coroutines to operate on the main thread.
However I can't use runBlocking
to create an event loop in the context of a full iOS app. I tried below, but it obviously doesn't work because UIApplicationMain(...)
never yields to runBlocking
's event loop.
// The app's main.swift entrypoint (simplified)
import UIKit
import KotlinFramework
KotlinFramework.runBlocking {
UIApplicationMain(
CommandLine.argc,
UnsafeMutableRawPointer(CommandLine.unsafeArgv).bindMemory(to: UnsafeMutablePointer<Int8>.self, capacity: Int(CommandLine.argc)),
nil,
NSStringFromClass(AppDelegate.self))
}
I'm looking into implementing my own iOS main event loop inside runBlocking
instead of using UIApplicationMain(...)
, but so far it doesn't look straightforward at all...
The alternative could be to implement a CoroutineDispatcher
as a wrapper around iOS's main NSRunLoop
(for example). Is this what you refer to as being too bug-prone?
Is there a way that I can get single-threaded coroutines running in a full app while we wait for #462?
Well after giving that a go, it seems to work just fine as a temporary solution:
object MainLoopDispatcher: CoroutineDispatcher() {
override fun dispatch(context: CoroutineContext, block: Runnable) {
NSRunLoop.mainRunLoop().performBlock {
block.run()
}
}
}
@brettwillis solution works as a charm. Make sure you removed delay()
calls from your coroutines if you still get the error. Spent some time trying to figure that out
@kamerok , below is the implementation I'm currently using. Updated for coroutines 1.0 and implemented the delay parts, so you don't have to remove delay()
calls.
@UseExperimental(InternalCoroutinesApi::class)
private object MainLoopDispatcher: CoroutineDispatcher(), Delay {
override fun dispatch(context: CoroutineContext, block: Runnable) {
dispatch_async(dispatch_get_main_queue()) {
try {
block.run()
} catch (err: Throwable) {
logError("UNCAUGHT", err.message ?: "", err)
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) {
logError("UNCAUGHT", err.message ?: "", err)
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) {
logError("UNCAUGHT", err.message ?: "", err)
throw err
}
}
return handle
}
}
hi guys,
So a custom CoroutineDispatcher is needed in order to run main-thread-bound coroutines on iOS. Something like @brettwillis mentioned above.
@qwwdfsad , @elizarov , is that correct?
Hi, yes, it looks correct on the first glace (note that I haven't tested this code).
try/catch
blocks are not really necessary because coroutine machinery should catch and report all exceptions by itself.
Well after giving that a go, it seems to work just fine as a temporary solution:
object MainLoopDispatcher: CoroutineDispatcher() { override fun dispatch(context: CoroutineContext, block: Runnable) { NSRunLoop.mainRunLoop().performBlock { block.run() } } }
Is there an example usage of this? Where do I use the MainLoopDispatcher?
I'm having this same issue, as @sschilli mentioned, is there an example on how to use the MainLoopDispatcher
?
@FrancoSabadini @sschilli There's a working example in my (still evolving) Kotlin Multi-platform Template. In short; one has to combine a Dispatcher
with a root Job
which forms a CoroutineScope
- from which you can launch
child Job
s.
The template defines three such Coroutine scopes: for UI, Process and Network (this is not intrinsic to co-routines, just my own 'starting point' for handling concurrency in Application projects).
In the JVM/Android target these are appropriately designated to the UI thread, a thread-pool and a virtually limitless thread creator.
For the iOS target, all three currently have to be designated to a dispatcher using iOS's main thread, due to this Kotlin/Native limitation.
Abuse of the main thread has its pitfalls, but this is a working solution for many kinds of application, for now.
Thanks @chris-hatton, I figured out how to do it a couple of days back but that project is very useful! Thanks for sharing!
@kamerok , below is the implementation I'm currently using. Updated for coroutines 1.0 and implemented the delay parts, so you don't have to remove
delay()
calls.
@brettwillis I've tried using this implementation to launch on macOS but it isn't working. I am doing the following:
@Test
fun `test launch`() {
val scope = CoroutineScope(MainLoopDispatcher)
scope.launch {
println("hello world")
}
runBlocking {
delay(5000) // wait to allow job to execute
}
}
However, the block never gets executed (I never see "hello world" printed). Is there anything I am missing? I know MainLoopDispatcher.dispatch
gets called but it just seems to never execute the block that is passed to it.
I guess that using the version 1.4.2-native-mt we may stop using a custom Coroutine Dispatcher, just using the Main, is that right?
Fixed with 1.6.0-RC and new memory model: when it's enabled, global_queue
is used for Dispatchers.Default
and main queue for Dispatchers.Main
Hi, I am using
'org.jetbrains.kotlinx:kotlinx-coroutines-core-native:0.24.0'
withoutputKinds = [FRAMEWORK]
to generate framework targeting ios_x64I wrote some uint tests for the code which contains
launch
call and ran tests with Gradle without any problem, but I got this error when ran the code in iOS emulator.Do we now have default dispatcher and UI dispatcher support for iOS?