@@ -38,6 +38,76 @@ public protocol Executor: AnyObject, Sendable {
3838}
3939
4040/// A service that executes jobs.
41+ ///
42+ /// ### Custom Actor Executors
43+ /// By default, all actor types execute tasks on a shared global concurrent pool.
44+ /// The global pool does not guarantee any thread (or dispatch queue) affinity,
45+ /// so actors are free to use different threads as they execute tasks.
46+ ///
47+ /// > The runtime may perform various optimizations to minimize un-necessary
48+ /// > thread switching.
49+ ///
50+ /// Sometimes it is important to be able to customize the execution behavior
51+ /// of an actor. For example, when an actor is known to perform heavy blocking
52+ /// operations (such as IO), and we would like to keep this work *off* the global
53+ /// shared pool, as blocking it may prevent other actors from being responsive.
54+ ///
55+ /// You can implement a custom executor, by conforming a type to the
56+ /// ``SerialExecutor`` protocol, and implementing the ``enqueue(_:)`` method.
57+ ///
58+ /// Once implemented, you can configure an actor to use such executor by
59+ /// implementing the actor's ``Actor/unownedExecutor`` computed property.
60+ /// For example, you could accept an executor in the actor's initializer,
61+ /// store it as a variable (in order to retain it for the duration of the
62+ /// actor's lifetime), and return it from the `unownedExecutor` computed
63+ /// property like this:
64+ ///
65+ /// ```
66+ /// actor MyActor {
67+ /// let myExecutor: MyExecutor
68+ ///
69+ /// // accepts an executor to run this actor on.
70+ /// init(executor: MyExecutor) {
71+ /// self.myExecutor = executor
72+ /// }
73+ ///
74+ /// nonisolated var unownedExecutor: UnownedSerialExecutor {
75+ /// self.myExecutor.asUnownedSerialExecutor()
76+ /// }
77+ /// }
78+ /// ```
79+ ///
80+ /// It is also possible to use a form of shared executor, either created as a
81+ /// global or static property, which you can then re-use for every MyActor
82+ /// instance:
83+ ///
84+ /// ```
85+ /// actor MyActor {
86+ /// // Serial executor reused by *all* instances of MyActor!
87+ /// static let sharedMyActorsExecutor = MyExecutor() // implements SerialExecutor
88+ ///
89+ ///
90+ /// nonisolated var unownedExecutor: UnownedSerialExecutor {
91+ /// Self.sharedMyActorsExecutor.asUnownedSerialExecutor()
92+ /// }
93+ /// }
94+ /// ```
95+ ///
96+ /// In the example above, *all* "MyActor" instances would be using the same
97+ /// serial executor, which would result in only one of such actors ever being
98+ /// run at the same time. This may be useful if some of your code has some
99+ /// "specific thread" requirement when interoperating with non-Swift runtimes
100+ /// for example.
101+ ///
102+ /// Since the ``UnownedSerialExecutor`` returned by the `unownedExecutor`
103+ /// property *does not* retain the executor, you must make sure the lifetime of
104+ /// it extends beyond the lifetime of any actor or task using it, as otherwise
105+ /// it may attempt to enqueue work on a released executor object, causing a crash.
106+ /// The executor returned by unownedExecutor *must* always be the same object,
107+ /// and returning different executors can lead to unexpected behavior.
108+ ///
109+ /// Alternatively, you can also use existing serial executor implementations,
110+ /// such as Dispatch's `DispatchSerialQueue` or others.
41111@available ( SwiftStdlib 5 . 1 , * )
42112public protocol SerialExecutor : Executor {
43113 // This requirement is repeated here as a non-override so that we
0 commit comments