@@ -38,6 +38,76 @@ public protocol Executor: AnyObject, Sendable {
38
38
}
39
39
40
40
/// 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.
41
111
@available ( SwiftStdlib 5 . 1 , * )
42
112
public protocol SerialExecutor : Executor {
43
113
// This requirement is repeated here as a non-override so that we
0 commit comments