Skip to content

Commit e6b7e5e

Browse files
authored
Merge pull request swiftlang#36307 from ktoso/wip-yield
[Concurrency] Simple Task.yield implementation
2 parents 54a0e19 + 9949bf4 commit e6b7e5e

File tree

3 files changed

+81
-1
lines changed

3 files changed

+81
-1
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+
508+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
509+
extension Task {
510+
511+
/// Explicitly suspend the current task, potentially giving up execution actor
512+
/// of current actor/task, allowing other tasks to execute.
513+
///
514+
/// This is not a perfect cure for starvation;
515+
/// if the task is the highest-priority task in the system, it might go
516+
/// immediately back to executing.
517+
public static func yield() async {
518+
// Prepare the job flags
519+
var flags = JobFlags()
520+
flags.kind = .task
521+
flags.priority = .default
522+
flags.isFuture = true
523+
524+
// Create the asynchronous task future, it will do nothing, but simply serves
525+
// as a way for us to yield our execution until the executor gets to it and
526+
// resumes us.
527+
// TODO: consider if it would be useful for this task to be a child task
528+
let (task, _) = Builtin.createAsyncTaskFuture(flags.bits, {})
529+
530+
// Enqueue the resulting job.
531+
_enqueueJobGlobal(Builtin.convertTaskToJob(task))
532+
533+
let _ = await Handle<Void, Never>(task).get()
534+
}
535+
}
536+
506537
// ==== UnsafeCurrentTask ------------------------------------------------------
507538

508539
/// Calls the given closure with the with the "current" task in which this

stdlib/public/Concurrency/TaskGroup.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -708,7 +708,7 @@ extension TaskGroup: AsyncSequence {
708708
/// - SeeAlso: `TaskGroup.next()` for a detailed discussion its semantics.
709709
public mutating func next() async -> Element? {
710710
guard !finished else { return nil }
711-
guard let element = try await group.next() else {
711+
guard let element = await group.next() else {
712712
finished = true
713713
return nil
714714
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
7+
protocol Go: Actor {
8+
func go(times: Int) async -> Int
9+
}
10+
11+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
12+
extension Go {
13+
func go(times: Int) async -> Int {
14+
for i in 0...times {
15+
print("\(Self.self) @ \(i)")
16+
await Task.yield()
17+
}
18+
return times
19+
}
20+
}
21+
22+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
23+
actor One: Go {}
24+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
25+
actor Two: Go {}
26+
27+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
28+
func yielding() async {
29+
let one = One()
30+
let two = Two()
31+
await withTaskGroup(of: Int.self) { group in
32+
await group.spawn {
33+
await one.go(times: 100)
34+
}
35+
await group.spawn {
36+
await two.go(times: 100)
37+
}
38+
}
39+
}
40+
41+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
42+
@main struct Main {
43+
static func main() async {
44+
await yielding()
45+
// TODO: No idea for a good test for this... Open to ideas?
46+
// CHECK: One
47+
// CHECK-NEXT: Two
48+
}
49+
}

0 commit comments

Comments
 (0)