You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: proposals/0417-task-executor-preference.md
+16-16
Original file line number
Diff line number
Diff line change
@@ -10,7 +10,7 @@
10
10
11
11
Swift Concurrency uses tasks and actors to model concurrency and primarily relies on actor isolation to determine where a specific piece of code shall execute.
12
12
13
-
The recent introduction of custom actor executors in [SE-0392](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0392-custom-actor-executors.md) allows customizing on what specific`SerialExecutor` implementation code should be running on while isolated to a specific actor. This allows developers to gain some control over exact threading semantics of actors, by e.g. making sure all work made by a specific actor is made on a dedicated queue or thread.
13
+
The recent introduction of custom actor executors in [SE-0392](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0392-custom-actor-executors.md) allows specifying a`SerialExecutor` implementation code should be running on while isolated to a specific actor. This allows developers to gain some control over exact threading semantics of actors, by e.g. making sure all work made by a specific actor is made on a dedicated queue or thread.
14
14
15
15
Today, the same flexibility is not available to tasks in general, and nonisolated asynchronous functions are always executed on the default global concurrent thread pool managed by Swift concurrency.
16
16
@@ -23,7 +23,7 @@ Notably, since Swift 5.7’s [SE-0338: Clarify the Execution of Non-Actor-Isolat
23
23
As Swift concurrency is getting adopted in a wider variety of performance sensitive codebases, it has become clear that the lack of control over where nonisolated functions execute is a noticeable problem.
24
24
At the same time, the defensive "hop-off" semantics introduced by [SE-0338](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0338-clarify-execution-non-actor-async.md) are still valuable, but sometimes too restrictive and some use-cases might even say that the exact opposite behavior might be desirable instead.
25
25
26
-
This proposal acknowledges the different needs of various use-cases, and provides a new flexible mechanism for developers to tune their applications and avoid potentially un-necessary context switching when possible.
26
+
This proposal acknowledges the different needs of various use-cases, and provides a new flexible mechanism for developers to tune their applications and avoid potentially unnecessary context switching when possible.
It is possible to declare a single executor type and have it conform to *both* the `SerialExecutor` (introduced in the custom actor executors proposal),
485
485
as well as the `TaskExecutor` (introduce in this proposal).
486
486
487
-
If declaring an executor that adheres to both protocols like that, it truly **must** adhere to the `SerialExecutor`
487
+
If declaring an executor which conforms to both protocols, it truly **must** adhere to the `SerialExecutor`
488
488
semantics of not running work concurrently, as it may be used as an *isolation context* by an actor.
489
489
490
490
```swift
@@ -519,11 +519,11 @@ final class NaiveQueueExecutor: TaskExecutor, SerialExecutor {
519
519
520
520
Since the enqueue method shares the same signature between the two protocols it is possible to just implement it once.
521
521
It is of crucial importance to run the job using the new `runSynchronously(isolatedOn:taskExecutor:)` overload
522
-
of the `runSynchronously` method. because it will set up all the required thread-local state for both isolation assertions
522
+
of the `runSynchronously` method. This will set up all the required thread-local state for both isolation assertions
523
523
and task-executor preference semantics to be handled properly.
524
524
525
-
Given such executor, we are able to have it both be used by an actor (thanks to being a `SerialExecutor`), as well as have
526
-
any structured tasks or nonisolated async functions keep executing on this executor (thanks to it being a `TaskExecutor`):
525
+
Given such an executor, we are able to have it both be used by an actor (thanks to being a `SerialExecutor`), and have
526
+
any structured tasks or nonisolated async functions execute on it (thanks to it being a `TaskExecutor`):
As with many new capabilities in libraries and languages, one may be tempted to use task executors to solve various problems.
613
613
614
-
We advise care when doing so with task executors, because while they do minimize the "hopping off"from executors and the associated context switching,
614
+
We advise care when doing so with task executors, because while they do minimize the "hopping off"of executors and the associated context switching,
615
615
this is also a behavior that may be entirely _undesirable_ in some situations. For example, over-hanging on the MainActor's executor is one of the main reasons
616
616
earlier Swift versions moved to make `nonisolated` asynchronous functions always hop off their calling execution context; and this proposal brings back this behavior
617
617
for specific executors.
@@ -631,7 +631,7 @@ The semantics explained in this proposal may at first seem tricky, however in re
631
631
632
632
It is worth discussing how user-control is retained with this proposal. Most notably, we believe this proposal follows Swift's core principle of progressive disclosure.
633
633
634
-
When developing an application at first one does not have to optimize forless context switches, however as applications grow performance analysis diagnoses context switching being a problem-- this proposal gives developers the tools to, selectively, in specific parts of a code-base introduce sticky task executor behavior.
634
+
When developing an application at first one does not have to optimize forfewer context switches, however ifas applications grow performance analysis diagnoses context switching being a problem this proposal gives developers the tools to, selectively, in specific parts of a code-base introduce sticky task executor behavior.
635
635
636
636
### Separating blocking code off the global shared pools
This way we won't be blocking threads inside the shared pool, and not risking thread starving of the entire application.
660
+
This way we won't be blocking threads inside the shared pool, and are not risking thread starving the entire application.
661
661
662
-
We can call `callRead` from inside `callBulk` and avoid un-necessary context switching as the same thread servicing the IO operation may be used for those asynchronous functions -- and no actual context switch may need to be performed when `callBulk` calls into `callRead` either.
662
+
We can call `callRead` from inside `callBulk` and avoid unnecessary context switching as the same thread servicing the IO operation may be used for those asynchronous functions -- and no actual context switch may need to be performed when `callBulk` calls into `callRead` either.
663
663
664
-
For end-users of this library the API they don't need to worry about any of this, but the author of such library isin full control over where execution will happen -- be it using task executor preference, or custom actor executors.
664
+
End-users of this library don't need to worry about any of this, but the author of such a library isin full control over where execution will happen -- be it using task executor preference, or custom actor executors.
665
665
666
-
This works also the other way around: when we're using a library and notice that it is doing blocking things and we'd rather separate it out onto a different executor. It may even have declared asynchronous methods-- but still is taking too long to yield the thread for some reason, causing issues to the shared pool.
666
+
This also works the other way around, when a user of a library notices that it is doing blocking work which they would rather separate out onto a different executor. This istrueeven if the library has declared asynchronous methods but still is taking too long to yield the thread for some reason, causing issues to the shared pool.
667
667
668
668
```swift
669
669
// SomeLibrary
@@ -683,9 +683,9 @@ func caller() async {
683
683
}
684
684
```
685
685
686
-
In other words, task executor preference gives control to developers at when and where care needs to be taken.
686
+
In other words, task executor preference gives control to developers when and where care needs to be taken.
687
687
688
-
The default of hop-avoiding when a preference issetis also a good default because it optimizesfor less context switching and can lead to better performance.
688
+
The default of hop-avoiding when a preference issethas the benefit of optimizingfor less context switching and can lead to better performance.
689
689
690
690
It is possible to effectively restore the default behavior as-if no task executor preference was present, by setting the preference to the `globalConcurrentExecutor` which is the executor used by default actors, tasks, and free async functions when no task executor preference isset:
0 commit comments