@@ -65,37 +65,78 @@ public abstract class CoroutineDispatcher :
65
65
66
66
/* *
67
67
* Creates a view of the current dispatcher that limits the parallelism to the given [value][parallelism].
68
- * The resulting view uses the original dispatcher for execution, but with the guarantee that
68
+ * The resulting view uses the original dispatcher for execution but with the guarantee that
69
69
* no more than [parallelism] coroutines are executed at the same time.
70
70
*
71
71
* This method does not impose restrictions on the number of views or the total sum of parallelism values,
72
72
* each view controls its own parallelism independently with the guarantee that the effective parallelism
73
73
* of all views cannot exceed the actual parallelism of the original dispatcher.
74
74
*
75
- * ### Limitations
76
- *
77
- * The default implementation of `limitedParallelism` does not support direct dispatchers,
78
- * such as executing the given runnable in place during [dispatch] calls.
79
- * Any dispatcher that may return `false` from [isDispatchNeeded] is considered direct.
80
- * For direct dispatchers, it is recommended to override this method
81
- * and provide a domain-specific implementation or to throw an [UnsupportedOperationException].
75
+ * The resulting dispatcher does not guarantee that the coroutines will always be dispatched on the same
76
+ * subset of threads, it only guarantees that at most [parallelism] coroutines are executed at the same time,
77
+ * and reuses threads from the original dispatchers.
78
+ * It does not constitute a resource -- it is a _view_ of the underlying dispatcher that can be thrown away
79
+ * and is not required to be closed.
82
80
*
83
81
* ### Example of usage
84
82
* ```
85
- * private val backgroundDispatcher = newFixedThreadPoolContext(4, "App Background")
83
+ * // Background dispatcher for the application
84
+ * val dispatcher = newFixedThreadPoolContext(4, "App Background")
86
85
* // At most 2 threads will be processing images as it is really slow and CPU-intensive
87
- * private val imageProcessingDispatcher = backgroundDispatcher .limitedParallelism(2)
86
+ * val imageProcessingDispatcher = dispatcher .limitedParallelism(2)
88
87
* // At most 3 threads will be processing JSON to avoid image processing starvation
89
- * private val jsonProcessingDispatcher = backgroundDispatcher .limitedParallelism(3)
88
+ * val jsonProcessingDispatcher = dispatcher .limitedParallelism(3)
90
89
* // At most 1 thread will be doing IO
91
- * private val fileWriterDispatcher = backgroundDispatcher .limitedParallelism(1)
90
+ * val fileWriterDispatcher = dispatcher .limitedParallelism(1)
92
91
* ```
93
92
* Note how in this example the application has an executor with 4 threads, but the total sum of all limits
94
- * is 6. Still, at most 4 coroutines can be executed simultaneously as each view limits only its own parallelism.
93
+ * is 6. Still, at most 4 coroutines can be executed simultaneously as each view limits only its own parallelism,
94
+ * and at most 4 threads can exist in the system.
95
95
*
96
96
* Note that this example was structured in such a way that it illustrates the parallelism guarantees.
97
- * In practice, it is usually better to use [Dispatchers.IO] or [Dispatchers.Default] instead of creating a
98
- * `backgroundDispatcher`. It is both possible and advised to call `limitedParallelism` on them.
97
+ * In practice, it is usually better to use `Dispatchers.IO` or [Dispatchers.Default] instead of creating a
98
+ * `backgroundDispatcher`.
99
+ *
100
+ * ### `limitedParallelism(1)` pattern
101
+ *
102
+ * One of the common patterns is confining the execution of specific tasks to a sequential execution in background
103
+ * with `limitedParallelism(1)` invocation.
104
+ * For that purpose, the implementation guarantees that tasks are executed sequentially and that a happens-before relation
105
+ * is established between them:
106
+ *
107
+ * ```
108
+ * val confined = Dispatchers.Default.limitedParallelism(1)
109
+ * var counter = 0
110
+ *
111
+ * // Invoked from arbitrary coroutines
112
+ * launch(confined) {
113
+ * // This increment is sequential and race-free
114
+ * ++counter
115
+ * }
116
+ * ```
117
+ * Note that there is no guarantee that the underlying system thread will always be the same.
118
+ *
119
+ * ### Dispatchers.IO
120
+ *
121
+ * `Dispatcher.IO` is considered _elastic_ for the purposes of limited parallelism -- the sum of
122
+ * views is not restricted by the capacity of `Dispatchers.IO`.
123
+ * It means that it is safe to replace `newFixedThreadPoolContext(nThreads)` with
124
+ * `Dispatchers.IO.limitedParallelism(nThreads)` w.r.t. available number of threads.
125
+ * See `Dispatchers.IO` documentation for more details.
126
+ *
127
+ * ### Restrictions and implementation details
128
+ *
129
+ * The default implementation of `limitedParallelism` does not support direct dispatchers,
130
+ * such as executing the given runnable in place during [dispatch] calls.
131
+ * Any dispatcher that may return `false` from [isDispatchNeeded] is considered direct.
132
+ * For direct dispatchers, it is recommended to override this method
133
+ * and provide a domain-specific implementation or to throw an [UnsupportedOperationException].
134
+ *
135
+ * Implementations of this method are allowed to return `this` if the current dispatcher already satisfies the parallelism requirement.
136
+ * For example, `Dispatchers.Main.limitedParallelism(1)` returns `Dispatchers.Main`, because the main dispatcher is already single-threaded.
137
+ *
138
+ * @throws IllegalArgumentException if the given [parallelism] is non-positive
139
+ * @throws UnsupportedOperationException if the current dispatcher does not support limited parallelism views
99
140
*/
100
141
@ExperimentalCoroutinesApi
101
142
public open fun limitedParallelism (parallelism : Int ): CoroutineDispatcher {
0 commit comments