Last active
April 5, 2024 22:38
-
-
Save fbartho/e8ac00e0827f3c7976da7bfce0fe51ed to your computer and use it in GitHub Desktop.
Swift Structured Concurrency - Actors and Rescheduling
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
actor Scheduler { | |
var tasks: [@Sendable () async -> Void] | |
func schedule(task: @escaping @Sendable () async -> Void) { | |
tasks.append(task) | |
} | |
func executeAll() async { | |
while let t = tasks.popLast() { | |
// Tasks may schedule more tasks when they execute | |
await t() | |
// How do I defer this loop until any pending schedule operations hit my actor? | |
} | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
actor Scheduler { | |
var tasks: [@Sendable () async -> Void] | |
func schedule(task: @escaping @Sendable () async -> Void) { | |
tasks.append(task) | |
} | |
func executeAll() async { | |
while let t = tasks.popLast() { | |
// Tasks may schedule more tasks when they execute | |
await t() | |
// Slot a task on the main actor, so any pending stuff has a | |
// chance to get scheduled before the loop resumes. | |
let flush = @MainActor () async -> { await self.ready() } | |
await flush() | |
// Is this await blocking the Scheduler actor? | |
// Does resuming the loop "cut the line" or is this resumption | |
// like enqueuing at the back of the actor's mailbox? | |
} | |
} | |
private func ready() async { | |
// Does nothing | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// main.swift | |
// TestCLI | |
// | |
// Created by Frederic Barthelemy on 4/5/24. | |
// | |
import Foundation | |
print("Hello, World!") | |
actor Scheduler { | |
var tasks: [@Sendable () async -> Void] = [] | |
func schedule(task: @escaping @Sendable () async -> Void) { | |
print("Scheduling!") | |
tasks.append(task) | |
} | |
func executeAll() async { | |
print("Beginning Execute") | |
// do { | |
// try await Task.sleep(nanoseconds: 1_000_000_000) | |
// } catch {} | |
// print("Post Sleep") | |
while !tasks.isEmpty { | |
print("Loop: Start") | |
var batch = Array(tasks.reversed()) | |
tasks = [] | |
while let t = batch.popLast() { | |
print("Loop: Entry") | |
// Tasks may schedule more tasks when they execute | |
await t() | |
} | |
print("Loop: Post-batch") | |
// Slot a task on the main actor, so any pending stuff has a | |
// chance to get scheduled before the loop resumes. | |
await flush() | |
print("Loop: Post-flush") | |
} | |
print("Done with execute all") | |
} | |
@MainActor private func flush() async { | |
print("Pre-flush") | |
await self.ready() | |
print("Post flush") | |
} | |
private func ready() async { | |
// Does nothing | |
print("Ready") | |
} | |
} | |
let s = Scheduler() | |
print("1.s") | |
await s.schedule { | |
print("1.1") | |
await s.schedule { | |
print("1.2") | |
} | |
print("1.1e") | |
} | |
print("1.e") | |
await s.schedule { | |
print("2.1") | |
await s.schedule { | |
print("2.2") | |
} | |
print("2.1e") | |
} | |
await s.schedule { | |
print("3.1") | |
await s.schedule { | |
print("3.2") | |
} | |
print("3.1e") | |
} | |
let t = Task { | |
print("enqueuing execute") | |
await s.executeAll() | |
await s.schedule { | |
print("post-all.1") | |
await s.schedule { | |
print("post-all.2") | |
} | |
print("post-all.1e") | |
} | |
} | |
await s.schedule { | |
print("4.1") | |
await s.schedule { | |
print("4.2") | |
} | |
print("4.1e") | |
} | |
await s.schedule { | |
print("5.1") | |
await s.schedule { | |
print("5.2") | |
} | |
print("5.1e") | |
} | |
print("wait for the executeAll task") | |
await t | |
print("done waiting for executeAll") | |
print("spinloop") | |
var count = 0 | |
while count < 1000 { | |
print("spin \(count)") | |
do { | |
try await Task.sleep(nanoseconds: 1_000_000_000) | |
} catch {} | |
count += 1 | |
} | |
print("Done") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment