@@ -65,37 +65,78 @@ public abstract class CoroutineDispatcher :
6565
6666 /* *
6767 * 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
6969 * no more than [parallelism] coroutines are executed at the same time.
7070 *
7171 * This method does not impose restrictions on the number of views or the total sum of parallelism values,
7272 * each view controls its own parallelism independently with the guarantee that the effective parallelism
7373 * of all views cannot exceed the actual parallelism of the original dispatcher.
7474 *
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.
8280 *
8381 * ### Example of usage
8482 * ```
85- * private val backgroundDispatcher = newFixedThreadPoolContext(4, "App Background")
83+ * // Background dispatcher for the application
84+ * val dispatcher = newFixedThreadPoolContext(4, "App Background")
8685 * // 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)
8887 * // 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)
9089 * // At most 1 thread will be doing IO
91- * private val fileWriterDispatcher = backgroundDispatcher .limitedParallelism(1)
90+ * val fileWriterDispatcher = dispatcher .limitedParallelism(1)
9291 * ```
9392 * 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.
9595 *
9696 * 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
99140 */
100141 @ExperimentalCoroutinesApi
101142 public open fun limitedParallelism (parallelism : Int ): CoroutineDispatcher {
0 commit comments