Skip to content

Commit a180f53

Browse files
committed
[Concurrency] Simple Task.yield implementation
1 parent a5a79f2 commit a180f53

File tree

2 files changed

+73
-0
lines changed

2 files changed

+73
-0
lines changed

stdlib/public/Concurrency/Task.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,37 @@ extension Task {
503503
}
504504
}
505505

506+
// ==== Voluntary Suspension -----------------------------------------------------
507+
extension Task {
508+
509+
/// Explicitly suspend the current task, potentially giving up execution actor
510+
/// of current actor/task, allowing other tasks to execute.
511+
///
512+
/// This is not a perfect cure for starvation;
513+
/// if the task is the highest-priority task in the system, it might go
514+
/// immediately back to executing.
515+
public static func yield() async {
516+
// Prepare the job flags
517+
var flags = JobFlags()
518+
flags.kind = .task
519+
flags.priority = .default
520+
flags.isFuture = true
521+
522+
// Create the asynchronous task future, it will do nothing, but simply serves
523+
// as a way for us to yield our execution until the executor gets to it and
524+
// resumes us.
525+
// FIXME: This should be an empty closure instead. Returning `0` here is
526+
// a workaround for rdar://74957357
527+
// TODO: consider if it would be useful for this task to be a child task
528+
let (task, _) = Builtin.createAsyncTaskFuture(flags.bits, nil, { return 0 })
529+
530+
// Enqueue the resulting job.
531+
_enqueueJobGlobal(Builtin.convertTaskToJob(task))
532+
533+
let _ = await Handle<Int, Never>(task).get()
534+
}
535+
}
536+
506537
// ==== UnsafeCurrentTask ------------------------------------------------------
507538

508539
/// Calls the given closure with the with the "current" task in which this
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// RUN: %target-run-simple-swift(-Xfrontend -enable-experimental-concurrency %import-libdispatch -parse-as-library) | %FileCheck %s
2+
3+
// REQUIRES: executable_test
4+
// REQUIRES: concurrency
5+
6+
protocol Go: Actor {
7+
func go(times: Int) async -> Int
8+
}
9+
extension Go {
10+
func go(times: Int) async -> Int {
11+
for i in 0...times {
12+
print("\(Self.self) @ \(i)")
13+
await Task.yield()
14+
}
15+
return times
16+
}
17+
}
18+
19+
actor One: Go {}
20+
actor Two: Go {}
21+
22+
func yielding() async {
23+
let one = One()
24+
let two = Two()
25+
try! await Task.withGroup(resultType: Int.self) { group in
26+
await group.add {
27+
await one.go(times: 100)
28+
}
29+
await group.add {
30+
await two.go(times: 100)
31+
}
32+
}
33+
}
34+
35+
@main struct Main {
36+
static func main() async {
37+
await yielding()
38+
// TODO: No idea for a good test for this... Open to ideas?
39+
// CHECK: One
40+
// CHECK-NEXT: Two
41+
}
42+
}

0 commit comments

Comments
 (0)