Skip to content

Commit cfe699f

Browse files
authored
Move UI dispatcher to common Dispatchers.Main (#641)
* Main thread dispatchers are grouped under Dispatchers object and are discoverable via ServiceLoader * Dispatchers.Main can be consistently used and easily discovered, its implementation is discovered with ServiceLoader * It allows us to add iOS Dispatchers.Main implementation in the future thus opening the door for multiplatform UI-dispatched code * Workaround for #626, now Android users can start migration to RC coroutines Fixed #626
1 parent 73bd3a9 commit cfe699f

File tree

21 files changed

+340
-79
lines changed

21 files changed

+340
-79
lines changed

README.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@ GlobalScope.launch {
2121
* [common](common/README.md) — common coroutines across all backends:
2222
* `launch` and `async` coroutine builders;
2323
* `Job` and `Deferred` light-weight future with cancellation support;
24+
*` Dispatchers.Main` for UI dispatcher for Android, Swing and JavaFx;
2425
* `delay` and `yield` top-level suspending functions;
2526
* `Channel` and `Mutex` communication and synchronization primitives;
2627
* `produce` and `actor` coroutine builders;
28+
* `coroutineScope` and `supervisorScope` scope builders;
29+
* `SupervisorJob` for supervision of coroutines hierarchies;
2730
* `select` expression support and more.
2831
* [core](core/README.md) — Kotlin/JVM implementation of common coroutines with additional features:
2932
* `Dispatchers.IO` dispatcher for blocking coroutines.
@@ -39,8 +42,8 @@ GlobalScope.launch {
3942
## Documentation
4043

4144
* Presentations and videos:
42-
* [Introduction to Coroutines](https://www.youtube.com/watch?v=_hfBv0a09Jc) (Roman Elizarov at KotlinConf 2017, [slides](https://www.slideshare.net/elizarov/introduction-to-coroutines-kotlinconf-2017))
43-
* [Deep dive into Coroutines](https://www.youtube.com/watch?v=YrrUCSi72E8) (Roman Elizarov at KotlinConf 2017, [slides](https://www.slideshare.net/elizarov/deep-dive-into-coroutines-on-jvm-kotlinconf-2017))
45+
* [Introduction to Coroutines](https://www.youtube.com/watch?v=_hfBv0a09Jc) (Roman Elizarov at KotlinConf 2017, [slides](https://www.slideshare.net/elizarov/introduction-to-coroutines-kotlinconf-2017))
46+
* [Deep dive into Coroutines](https://www.youtube.com/watch?v=YrrUCSi72E8) (Roman Elizarov at KotlinConf 2017, [slides](https://www.slideshare.net/elizarov/deep-dive-into-coroutines-on-jvm-kotlinconf-2017))
4447
* Guides and manuals:
4548
* [Guide to kotlinx.coroutines by example](docs/coroutines-guide.md) (**read it first**)
4649
* [Guide to UI programming with coroutines](ui/coroutines-guide-ui.md)

binary-compatibility-validator/reference-public-api/kotlinx-coroutines-android.txt

+3-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ public final class kotlinx/coroutines/experimental/android/HandlerContext : kotl
44
public final fun awaitFrame (Lkotlin/coroutines/experimental/Continuation;)Ljava/lang/Object;
55
public fun dispatch (Lkotlin/coroutines/experimental/CoroutineContext;Ljava/lang/Runnable;)V
66
public fun equals (Ljava/lang/Object;)Z
7+
public synthetic fun getImmediate ()Lkotlinx/coroutines/experimental/MainCoroutineDispatcher;
78
public fun getImmediate ()Lkotlinx/coroutines/experimental/android/HandlerContext;
89
public synthetic fun getImmediate ()Lkotlinx/coroutines/experimental/android/HandlerDispatcher;
910
public fun hashCode ()I
@@ -18,7 +19,7 @@ public final class kotlinx/coroutines/experimental/android/HandlerContextKt {
1819
public static final fun getUI ()Lkotlinx/coroutines/experimental/android/HandlerContext;
1920
}
2021

21-
public abstract class kotlinx/coroutines/experimental/android/HandlerDispatcher : kotlinx/coroutines/experimental/CoroutineDispatcher, kotlinx/coroutines/experimental/Delay {
22+
public abstract class kotlinx/coroutines/experimental/android/HandlerDispatcher : kotlinx/coroutines/experimental/MainCoroutineDispatcher, kotlinx/coroutines/experimental/Delay {
2223
public synthetic fun delay (JLjava/util/concurrent/TimeUnit;Lkotlin/coroutines/experimental/Continuation;)Ljava/lang/Object;
2324
public fun delay (JLkotlin/coroutines/experimental/Continuation;)Ljava/lang/Object;
2425
public abstract fun getImmediate ()Lkotlinx/coroutines/experimental/android/HandlerDispatcher;
@@ -32,6 +33,6 @@ public final class kotlinx/coroutines/experimental/android/HandlerDispatcherKt {
3233
public static final fun from (Landroid/os/Handler;)Lkotlinx/coroutines/experimental/android/HandlerDispatcher;
3334
public static final fun from (Landroid/os/Handler;Ljava/lang/String;)Lkotlinx/coroutines/experimental/android/HandlerDispatcher;
3435
public static synthetic fun from$default (Landroid/os/Handler;Ljava/lang/String;ILjava/lang/Object;)Lkotlinx/coroutines/experimental/android/HandlerDispatcher;
35-
public static final fun getMain (Lkotlinx/coroutines/experimental/Dispatchers;)Lkotlinx/coroutines/experimental/android/HandlerDispatcher;
36+
public static final synthetic fun getMain (Lkotlinx/coroutines/experimental/Dispatchers;)Lkotlinx/coroutines/experimental/android/HandlerDispatcher;
3637
}
3738

binary-compatibility-validator/reference-public-api/kotlinx-coroutines-core.txt

+9-3
Original file line numberDiff line numberDiff line change
@@ -314,14 +314,15 @@ public final class kotlinx/coroutines/experimental/DispatchedTask$DefaultImpls {
314314
}
315315

316316
public final class kotlinx/coroutines/experimental/Dispatchers {
317-
public static final field Default Lkotlinx/coroutines/experimental/CoroutineDispatcher;
318317
public static final field INSTANCE Lkotlinx/coroutines/experimental/Dispatchers;
319-
public static final field Unconfined Lkotlinx/coroutines/experimental/CoroutineDispatcher;
318+
public static final fun getDefault ()Lkotlinx/coroutines/experimental/CoroutineDispatcher;
319+
public static final fun getIO ()Lkotlinx/coroutines/experimental/CoroutineDispatcher;
320+
public static final fun getMain ()Lkotlinx/coroutines/experimental/MainCoroutineDispatcher;
321+
public static final fun getUnconfined ()Lkotlinx/coroutines/experimental/CoroutineDispatcher;
320322
}
321323

322324
public final class kotlinx/coroutines/experimental/DispatchersKt {
323325
public static final field IO_PARALLELISM_PROPERTY_NAME Ljava/lang/String;
324-
public static final fun getIO (Lkotlinx/coroutines/experimental/Dispatchers;)Lkotlinx/coroutines/experimental/CoroutineDispatcher;
325326
}
326327

327328
public abstract interface class kotlinx/coroutines/experimental/DisposableHandle {
@@ -468,6 +469,11 @@ public final class kotlinx/coroutines/experimental/LazyDeferredKt {
468469
public static final fun lazyDefer (Lkotlin/coroutines/experimental/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/experimental/Deferred;
469470
}
470471

472+
public abstract class kotlinx/coroutines/experimental/MainCoroutineDispatcher : kotlinx/coroutines/experimental/CoroutineDispatcher {
473+
public fun <init> ()V
474+
public abstract fun getImmediate ()Lkotlinx/coroutines/experimental/MainCoroutineDispatcher;
475+
}
476+
471477
public final class kotlinx/coroutines/experimental/NonCancellable : kotlin/coroutines/experimental/AbstractCoroutineContextElement, kotlinx/coroutines/experimental/Job {
472478
public static final field INSTANCE Lkotlinx/coroutines/experimental/NonCancellable;
473479
public fun attachChild (Lkotlinx/coroutines/experimental/ChildJob;)Lkotlinx/coroutines/experimental/ChildHandle;

binary-compatibility-validator/reference-public-api/kotlinx-coroutines-javafx.txt

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
public final class kotlinx/coroutines/experimental/javafx/JavaFx : kotlinx/coroutines/experimental/javafx/JavaFxDispatcher {
22
public static final field INSTANCE Lkotlinx/coroutines/experimental/javafx/JavaFx;
33
public final fun awaitPulse (Lkotlin/coroutines/experimental/Continuation;)Ljava/lang/Object;
4-
public fun dispatch (Lkotlin/coroutines/experimental/CoroutineContext;Ljava/lang/Runnable;)V
5-
public fun invokeOnTimeout (JLjava/lang/Runnable;)Lkotlinx/coroutines/experimental/DisposableHandle;
6-
public fun scheduleResumeAfterDelay (JLkotlinx/coroutines/experimental/CancellableContinuation;)V
4+
public fun getImmediate ()Lkotlinx/coroutines/experimental/MainCoroutineDispatcher;
75
public fun toString ()Ljava/lang/String;
86
}
97

10-
public abstract class kotlinx/coroutines/experimental/javafx/JavaFxDispatcher : kotlinx/coroutines/experimental/CoroutineDispatcher, kotlinx/coroutines/experimental/Delay {
8+
public abstract class kotlinx/coroutines/experimental/javafx/JavaFxDispatcher : kotlinx/coroutines/experimental/MainCoroutineDispatcher, kotlinx/coroutines/experimental/Delay {
119
public synthetic fun delay (JLjava/util/concurrent/TimeUnit;Lkotlin/coroutines/experimental/Continuation;)Ljava/lang/Object;
1210
public fun delay (JLkotlin/coroutines/experimental/Continuation;)Ljava/lang/Object;
11+
public fun dispatch (Lkotlin/coroutines/experimental/CoroutineContext;Ljava/lang/Runnable;)V
1312
public fun invokeOnTimeout (JLjava/lang/Runnable;)Lkotlinx/coroutines/experimental/DisposableHandle;
1413
public synthetic fun invokeOnTimeout (JLjava/util/concurrent/TimeUnit;Ljava/lang/Runnable;)Lkotlinx/coroutines/experimental/DisposableHandle;
1514
public synthetic fun scheduleResumeAfterDelay (JLjava/util/concurrent/TimeUnit;Lkotlinx/coroutines/experimental/CancellableContinuation;)V
15+
public fun scheduleResumeAfterDelay (JLkotlinx/coroutines/experimental/CancellableContinuation;)V
1616
}
1717

1818
public final class kotlinx/coroutines/experimental/javafx/JavaFxDispatcherKt {

binary-compatibility-validator/reference-public-api/kotlinx-coroutines-swing.txt

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
public final class kotlinx/coroutines/experimental/swing/Swing : kotlinx/coroutines/experimental/swing/SwingDispatcher {
22
public static final field INSTANCE Lkotlinx/coroutines/experimental/swing/Swing;
3-
public fun dispatch (Lkotlin/coroutines/experimental/CoroutineContext;Ljava/lang/Runnable;)V
4-
public fun invokeOnTimeout (JLjava/lang/Runnable;)Lkotlinx/coroutines/experimental/DisposableHandle;
5-
public fun scheduleResumeAfterDelay (JLkotlinx/coroutines/experimental/CancellableContinuation;)V
3+
public fun getImmediate ()Lkotlinx/coroutines/experimental/MainCoroutineDispatcher;
64
public fun toString ()Ljava/lang/String;
75
}
86

9-
public abstract class kotlinx/coroutines/experimental/swing/SwingDispatcher : kotlinx/coroutines/experimental/CoroutineDispatcher, kotlinx/coroutines/experimental/Delay {
7+
public abstract class kotlinx/coroutines/experimental/swing/SwingDispatcher : kotlinx/coroutines/experimental/MainCoroutineDispatcher, kotlinx/coroutines/experimental/Delay {
108
public synthetic fun delay (JLjava/util/concurrent/TimeUnit;Lkotlin/coroutines/experimental/Continuation;)Ljava/lang/Object;
119
public fun delay (JLkotlin/coroutines/experimental/Continuation;)Ljava/lang/Object;
10+
public fun dispatch (Lkotlin/coroutines/experimental/CoroutineContext;Ljava/lang/Runnable;)V
1211
public fun invokeOnTimeout (JLjava/lang/Runnable;)Lkotlinx/coroutines/experimental/DisposableHandle;
1312
public synthetic fun invokeOnTimeout (JLjava/util/concurrent/TimeUnit;Ljava/lang/Runnable;)Lkotlinx/coroutines/experimental/DisposableHandle;
1413
public synthetic fun scheduleResumeAfterDelay (JLjava/util/concurrent/TimeUnit;Lkotlinx/coroutines/experimental/CancellableContinuation;)V
14+
public fun scheduleResumeAfterDelay (JLkotlinx/coroutines/experimental/CancellableContinuation;)V
1515
}
1616

1717
public final class kotlinx/coroutines/experimental/swing/SwingDispatcherKt {

common/kotlinx-coroutines-core-common/src/Dispatchers.common.kt

+24-9
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@
44

55
package kotlinx.coroutines.experimental
66

7-
import kotlinx.coroutines.experimental.internal.*
87
import kotlin.coroutines.experimental.*
98

109
/**
1110
* Groups various implementations of [CoroutineDispatcher].
1211
*/
13-
public object Dispatchers {
12+
expect object Dispatchers {
1413
/**
1514
* The default [CoroutineDispatcher] that is used by all standard builders like
1615
* [launch][CoroutineScope.launch], [async][CoroutineScope.async], etc
@@ -19,9 +18,27 @@ public object Dispatchers {
1918
* It is backed by a shared pool of threads on JVM. By default, the maximal number of threads used
2019
* by this dispatcher is equal to the number CPU cores, but is at least two.
2120
*/
22-
@JvmField
23-
public val Default: CoroutineDispatcher =
24-
createDefaultDispatcher()
21+
public val Default: CoroutineDispatcher
22+
23+
/**
24+
* A coroutine dispatcher that is confined to the Main thread operating with UI objects.
25+
* Usually such dispatcher is single-threaded.
26+
*
27+
* Access to this property may throw [IllegalStateException] if no main dispatchers are present in the classpath.
28+
*
29+
* Depending on platform and classpath it can be mapped to different dispatchers:
30+
* - On JS and Native it is equivalent of [Default] dispatcher.
31+
* - On JVM it either Android main thread dispatcher, JavaFx or Swing EDT dispatcher. It is chosen by
32+
* [`ServiceLoader`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html).
33+
*
34+
* In order to work with `Main` dispatcher, following artifact should be added to project runtime dependencies:
35+
* - `kotlinx-coroutines-android` for Android Main thread dispatcher
36+
* - `kotlinx-coroutines-javafx` for JavaFx Application thread dispatcher
37+
* - `kotlinx-coroutines-swing` for Swing EDT dispatcher
38+
*
39+
* Implementation note: [MainCoroutineDispatcher.immediate] is not supported on Native and JS platforms.
40+
*/
41+
public val Main: MainCoroutineDispatcher
2542

2643
/**
2744
* A coroutine dispatcher that is not confined to any specific thread.
@@ -39,8 +56,6 @@ public object Dispatchers {
3956
* **Note: This is an experimental api.**
4057
* Semantics, order of execution, and particular implementation details of this dispatcher may change in the future.
4158
*/
42-
@JvmField
4359
@ExperimentalCoroutinesApi
44-
public val Unconfined: CoroutineDispatcher =
45-
kotlinx.coroutines.experimental.Unconfined
46-
}
60+
public val Unconfined: CoroutineDispatcher
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.coroutines.experimental
6+
7+
/**
8+
* Base class for special [CoroutineDispatcher] which is confined to application "Main" or "UI" thread
9+
* and used for any UI-based activities. Instance of `MainDispatcher` can be obtained by [Dispatchers.Main].
10+
*
11+
* Platform may or may not provide instance of `MainDispatcher`, see documentation to [Dispatchers.Main]
12+
*/
13+
public abstract class MainCoroutineDispatcher : CoroutineDispatcher() {
14+
15+
/**
16+
* Returns dispatcher that executes coroutines immediately when it is already in the right context
17+
* (e.g. current looper is the same as this handler's looper). See [isDispatchNeeded] documentation on
18+
* why this should not be done by default.
19+
* Method may throw [UnsupportedOperationException] if immediate dispatching is not supported by current dispatcher,
20+
* please refer to specific dispatcher documentation.
21+
*
22+
* **Note: This is an experimental api.** Semantics of this dispatcher may change in the future.
23+
*/
24+
@ExperimentalCoroutinesApi
25+
public abstract val immediate: MainCoroutineDispatcher
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.coroutines.experimental.internal
6+
7+
import kotlinx.coroutines.experimental.*
8+
9+
@InternalCoroutinesApi // Emulating DI for Kotlin object's
10+
public interface MainDispatcherFactory {
11+
val loadPriority: Int // higher priority wins
12+
13+
fun createDispatcher(): MainCoroutineDispatcher
14+
}

core/kotlinx-coroutines-core/src/Dispatchers.kt

+82-12
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,94 @@
66

77
package kotlinx.coroutines.experimental
88

9+
import kotlinx.coroutines.experimental.internal.*
910
import kotlinx.coroutines.experimental.scheduling.*
11+
import java.util.*
12+
import kotlin.coroutines.experimental.*
1013

1114
/**
1215
* Name of the property that defines the maximal number of threads that are used by [Dispatchers.IO] coroutines dispatcher.
1316
*/
1417
public const val IO_PARALLELISM_PROPERTY_NAME = "kotlinx.coroutines.io.parallelism"
1518

1619
/**
17-
* The [CoroutineDispatcher] that is designed for offloading blocking IO tasks to a shared pool of threads.
18-
*
19-
* Additional threads in this pool are created and are shutdown on demand.
20-
* The number of threads used by this dispatcher is limited by the value of
21-
* "`kotlinx.coroutines.io.parallelism`" ([IO_PARALLELISM_PROPERTY_NAME]) system property.
22-
* It defaults to the limit of 64 threads or the number of cores (whichever is larger).
23-
*
24-
* This dispatcher shares threads with a [Default][Dispatchers.Default] dispatcher, so using
25-
* `withContext(Dispatchers.IO) { ... }` does not lead to an actual switching to another thread &mdash;
26-
* typically execution continues in the same thread.
20+
* Groups various implementations of [CoroutineDispatcher].
2721
*/
28-
public val Dispatchers.IO: CoroutineDispatcher
29-
get() = DefaultScheduler.IO
22+
actual object Dispatchers {
23+
24+
private val mainDispatcher = loadMainDispatcher()
25+
26+
private fun loadMainDispatcher(): MainCoroutineDispatcher? {
27+
return MainDispatcherFactory::class.java.let { clz ->
28+
ServiceLoader.load(clz, clz.classLoader).toList()
29+
}.maxBy { it.loadPriority }?.createDispatcher()
30+
}
31+
32+
/**
33+
* The default [CoroutineDispatcher] that is used by all standard builders like
34+
* [launch][CoroutineScope.launch], [async][CoroutineScope.async], etc
35+
* if no dispatcher nor any other [ContinuationInterceptor] is specified in their context.
36+
*
37+
* It is backed by a shared pool of threads on JVM. By default, the maximal number of threads used
38+
* by this dispatcher is equal to the number CPU cores, but is at least two.
39+
*/
40+
@JvmStatic
41+
public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
42+
43+
/**
44+
* A coroutine dispatcher that is confined to the Main thread operating with UI objects.
45+
* Usually such dispatcher is single-threaded.
46+
*
47+
* Access to this property may throw [IllegalStateException] if no main thread dispatchers are present in the classpath.
48+
*
49+
* Depending on platform and classpath it can be mapped to different dispatchers:
50+
* - On JS and Native it is equivalent of [Default] dispatcher.
51+
* - On JVM it either Android main thread dispatcher, JavaFx or Swing EDT dispatcher. It is chosen by
52+
* [`ServiceLoader`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html).
53+
*
54+
* In order to work with `Main` dispatcher, following artifact should be added to project runtime dependencies:
55+
* - `kotlinx-coroutines-android` for Android Main thread dispatcher
56+
* - `kotlinx-coroutines-javafx` for JavaFx Application thread dispatcher
57+
* - `kotlinx-coroutines-swing` for Swing EDT dispatcher
58+
*
59+
* Implementation note: [MainCoroutineDispatcher.immediate] is not supported on Native and JS platforms.
60+
*/
61+
@JvmStatic
62+
public actual val Main: MainCoroutineDispatcher get() = mainDispatcher ?: error("Module with Main dispatcher is missing. " +
63+
"Add dependency with required Main dispatcher, e.g. 'kotlinx-coroutines-android'")
64+
65+
/**
66+
* A coroutine dispatcher that is not confined to any specific thread.
67+
* It executes initial continuation of the coroutine _immediately_ in the current call-frame
68+
* and lets the coroutine resume in whatever thread that is used by the corresponding suspending function, without
69+
* mandating any specific threading policy.
70+
* **Note: use with extreme caution, not for general code**.
71+
*
72+
* Note, that if you need your coroutine to be confined to a particular thread or a thread-pool after resumption,
73+
* but still want to execute it in the current call-frame until its first suspension, then you can use
74+
* an optional [CoroutineStart] parameter in coroutine builders like
75+
* [launch][CoroutineScope.launch] and [async][CoroutineScope.async] setting it to the
76+
* the value of [CoroutineStart.UNDISPATCHED].
77+
*
78+
* **Note: This is an experimental api.**
79+
* Semantics, order of execution, and particular implementation details of this dispatcher may change in the future.
80+
*/
81+
@JvmStatic
82+
@ExperimentalCoroutinesApi
83+
public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.experimental.Unconfined
84+
85+
/**
86+
* The [CoroutineDispatcher] that is designed for offloading blocking IO tasks to a shared pool of threads.
87+
*
88+
* Additional threads in this pool are created and are shutdown on demand.
89+
* The number of threads used by this dispatcher is limited by the value of
90+
* "`kotlinx.coroutines.io.parallelism`" ([IO_PARALLELISM_PROPERTY_NAME]) system property.
91+
* It defaults to the limit of 64 threads or the number of cores (whichever is larger).
92+
*
93+
* This dispatcher shares threads with a [Default][Dispatchers.Default] dispatcher, so using
94+
* `withContext(Dispatchers.IO) { ... }` does not lead to an actual switching to another thread &mdash;
95+
* typically execution continues in the same thread.
96+
*/
97+
@JvmStatic
98+
public val IO: CoroutineDispatcher = DefaultScheduler.IO
99+
}

0 commit comments

Comments
 (0)