@@ -293,11 +293,10 @@ public protocol RunLoopExecutor: Executor {
293
293
}
294
294
```
295
295
296
- We will also add a protocol for the main actor's executor (see later
297
- for details of ` EventableExecutor ` and why it exists):
296
+ We will also add a protocol for the main actor's executor:
298
297
299
298
``` swift
300
- protocol MainExecutor : RunLoopExecutor & SerialExecutor & EventableExecutor {
299
+ protocol MainExecutor : RunLoopExecutor & SerialExecutor {
301
300
}
302
301
```
303
302
@@ -342,13 +341,33 @@ along with a default implementation of `ExecutorFactory` called
342
341
` PlatformExecutorFactory ` that sets the default executors for the
343
342
current platform.
344
343
345
- Additionally, ` Task ` will expose a new ` currentExecutor ` property:
344
+ Additionally, ` Task ` will expose a new ` currentExecutor ` property, as
345
+ well as properties for the ` preferredExecutor ` and the
346
+ ` currentSchedulableExecutor ` :
346
347
347
348
``` swift
348
349
extension Task {
349
350
/// Get the current executor; this is the executor that the currently
350
351
/// executing task is executing on.
351
- public static var currentExecutor: (any Executor)? { get }
352
+ ///
353
+ /// This will return, in order of preference:
354
+ ///
355
+ /// 1. The custom executor associated with an `Actor` on which we are
356
+ /// currently running, or
357
+ /// 2. The preferred executor for the currently executing `Task`, or
358
+ /// 3. The task executor for the current thread
359
+ /// 4. The default executor.
360
+ public static var currentExecutor: any Executor { get }
361
+
362
+ /// Get the preferred executor for the current `Task`, if any.
363
+ public static var preferredExecutor: (any TaskExecutor)? { get }
364
+
365
+ /// Get the current *schedulable* executor, if any.
366
+ ///
367
+ /// This follows the same logic as `currentExecutor`, except that it ignores
368
+ /// any executor that isn't a `SchedulableExecutor`, and as such it may
369
+ /// eventually return `nil`.
370
+ public static var currentSchedulableExecutor: (any SchedulableExecutor)? { get }
352
371
}
353
372
```
354
373
@@ -475,21 +494,24 @@ if let chunk = job.allocator?.allocate(capacity: 1024) {
475
494
}
476
495
```
477
496
478
- We will also round-out the ` Executor ` protocol with some ` Clock ` -based
479
- APIs to enqueue after a delay :
497
+ We will also add a ` SchedulableExecutor ` protocol as well as a way to
498
+ get it efficiently from an ` Executor ` :
480
499
481
500
``` swift
482
501
protocol Executor {
483
502
...
484
- /// `true` if this Executor supports scheduling.
503
+ /// Return this executable as a SchedulableExecutor, or nil if that is
504
+ /// unsupported.
485
505
///
486
- /// This will default to false. If you attempt to use the delayed
487
- /// enqueuing functions on an executor that does not support scheduling,
488
- /// the default executor will be used to do the scheduling instead,
489
- /// unless the default executor does not support scheduling in which
490
- /// case you will get a fatal error .
491
- var supportsScheduling: Bool { get }
506
+ /// Executors can implement this method explicitly to avoid the use of
507
+ /// a potentially expensive runtime cast.
508
+ @available (SwiftStdlib 6.2, * )
509
+ var asSchedulable: AsSchedulable ? { get }
510
+ .. .
511
+ }
492
512
513
+ protocol SchedulableExecutor : Executor {
514
+ ...
493
515
/// Enqueue a job to run after a specified delay.
494
516
///
495
517
/// You need only implement one of the two enqueue functions here;
@@ -537,14 +559,6 @@ APIs to get both of them working; there is a default implementation
537
559
that will do the necessary mathematics for you to implement the other
538
560
one.
539
561
540
- If you try to call the ` Clock ` -based ` enqueue ` APIs on an executor
541
- that does not declare support for them (by returning ` true ` from its
542
- ` supportsScheduling ` property), the runtime will raise a fatal error.
543
-
544
- (These functions have been added to the ` Executor ` protocol directly
545
- rather than adding a separate protocol to avoid having to do a dynamic
546
- cast at runtime, which is a relatively slow operation.)
547
-
548
562
To support these ` Clock ` -based APIs, we will add to the ` Clock `
549
563
protocol as follows:
550
564
@@ -616,9 +630,9 @@ The `traits` property is of type `ClockTraits`, which is an
616
630
/// basis.
617
631
@available (SwiftStdlib 6.2, * )
618
632
public struct ClockTraits : OptionSet {
619
- public let rawValue: Int32
633
+ public let rawValue: UInt32
620
634
621
- public init (rawValue : Int32 )
635
+ public init (rawValue : UInt32 )
622
636
623
637
/// Clocks with this trait continue running while the machine is asleep.
624
638
public static let continuous = ...
@@ -661,59 +675,6 @@ We will not be able to support the new `Clock`-based `enqueue` APIs on
661
675
Embedded Swift at present because it does not allow protocols to
662
676
contain generic functions.
663
677
664
- ### Coalesced Event Interface
665
-
666
- We would like custom main executors to be able to integrate with other
667
- libraries, without tying the implementation to a specific library; in
668
- practice, this means that the executor will need to be able to trigger
669
- processing from some external event.
670
-
671
- ``` swift
672
- protocol EventableExecutor {
673
-
674
- /// An opaque, executor-dependent type used to represent an event.
675
- associatedtype Event
676
-
677
- /// Register a new event with a given handler.
678
- ///
679
- /// Notifying the executor of the event will cause the executor to
680
- /// execute the handler, however the executor is free to coalesce multiple
681
- /// event notifications, and is also free to execute the handler at a time
682
- /// of its choosing.
683
- ///
684
- /// Parameters
685
- ///
686
- /// - handler: The handler to call when the event fires.
687
- ///
688
- /// Returns a new opaque `Event`.
689
- public func registerEvent (handler : @escaping @Sendable () -> ()) -> Event
690
-
691
- /// Deregister the given event.
692
- ///
693
- /// After this function returns, there will be no further executions of the
694
- /// handler for the given event.
695
- public func deregister (event : Event)
696
-
697
- /// Notify the executor of an event.
698
- ///
699
- /// This will trigger, at some future point, the execution of the associated
700
- /// event handler. Prior to that time, multiple calls to `notify` may be
701
- /// coalesced and result in a single invocation of the event handler.
702
- public func notify (event : Event)
703
-
704
- }
705
- ```
706
-
707
- Our expectation is that a library that wishes to integrate with the
708
- main executor will register an event with the main executor, and can
709
- then notify the main executor of that event, which will trigger the
710
- executor to run the associated handler at an appropriate time.
711
-
712
- The point of this interface is that a library can rely on the executor
713
- to coalesce these events, such that the handler will be triggered once
714
- for a potentially long series of ` MainActor.executor.notify(event:) `
715
- invocations.
716
-
717
678
### Overriding the main and default executors
718
679
719
680
Setting the executors directly is tricky because they might already be
@@ -888,6 +849,16 @@ break for places where Swift Concurrency already runs, because some
888
849
existing code already knows that it is not really asynchronous until
889
850
the first ` await ` in the main entry point.
890
851
852
+ ### Adding a coalesced event interface
853
+
854
+ A previous revision of this proposal included an ` EventableExecutor `
855
+ interface, which could be used to tie other libraries into a custom
856
+ executor without the custom executor needing to have specific
857
+ knowledge of those libraries.
858
+
859
+ While a good idea, it was decided that this would be better dealt with
860
+ as a separate proposal.
861
+
891
862
### Putting the new Clock-based enqueue functions into a protocol
892
863
893
864
It would be cleaner to have the new Clock-based enqueue functions in a
0 commit comments