Skip to content

Commit 623e54f

Browse files
authored
Merge pull request swiftlang#37007 from DougGregor/async-operation
[Concurrency] Add "async" operation for continuing work asynchronously.
2 parents 16adf76 + 181ffaf commit 623e54f

File tree

4 files changed

+139
-0
lines changed

4 files changed

+139
-0
lines changed

include/swift/Runtime/Concurrency.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,10 @@ SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
642642
void swift_task_reportUnexpectedExecutor(
643643
const unsigned char *file, uintptr_t fileLength, bool fileIsASCII,
644644
uintptr_t line, ExecutorRef executor);
645+
646+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
647+
JobPriority swift_task_getCurrentThreadPriority(void);
648+
645649
}
646650

647651
#pragma clang diagnostic pop

stdlib/public/Concurrency/Actor.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "swift/ABI/Actor.h"
2828
#include "llvm/ADT/PointerIntPair.h"
2929
#include "TaskPrivate.h"
30+
#include <dispatch/dispatch.h>
3031

3132
#if defined(__APPLE__)
3233
#include <asl.h>
@@ -259,6 +260,17 @@ static bool isExecutingOnMainThread() {
259260
#endif
260261
}
261262

263+
JobPriority swift::swift_task_getCurrentThreadPriority() {
264+
if (isExecutingOnMainThread())
265+
return JobPriority::UserInitiated;
266+
267+
#if defined(__APPLE__)
268+
return static_cast<JobPriority>(qos_class_self());
269+
#else
270+
return JobPriority::Unspecified;
271+
#endif
272+
}
273+
262274
SWIFT_CC(swift)
263275
static bool swift_task_isCurrentExecutorImpl(ExecutorRef executor) {
264276
if (auto currentTracking = ExecutorTrackingInfo::current()) {

stdlib/public/Concurrency/Task.swift

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,55 @@ public func detach<T>(
481481
return Task.Handle<T, Error>(task)
482482
}
483483

484+
/// Run given `operation` as asynchronously in its own top-level task.
485+
///
486+
/// The `async` function should be used when creating asynchronous work
487+
/// that operates on behalf of the synchronous function that calls it.
488+
/// Like `detach`, the async function creates a separate, top-level task.
489+
/// Unlike `detach`, the task creating by `async` inherits the priority and
490+
/// actor context of the caller, so the `operation` is treated more like an
491+
/// asynchronous extension to the synchronous operation. Additionally, `async`
492+
/// does not return a handle to refer to the task.
493+
///
494+
/// - Parameters:
495+
/// - priority: priority of the task. If unspecified, the priority will
496+
/// be inherited from the task that is currently executing
497+
/// or, if there is none, from the platform's understanding of
498+
/// which thread is executing.
499+
/// - operation: the operation to execute
500+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
501+
public func async(
502+
priority: Task.Priority = .unspecified,
503+
@_inheritActorContext @_implicitSelfCapture operation: __owned @Sendable @escaping () async -> Void
504+
) {
505+
// Determine the priority at which we should create this task
506+
let actualPriority: Task.Priority
507+
if priority == .unspecified {
508+
actualPriority = withUnsafeCurrentTask { task in
509+
// If we are running on behalf of a task,
510+
if let task = task {
511+
return task.priority
512+
}
513+
514+
return Task.Priority(rawValue: _getCurrentThreadPriority()) ?? .unspecified
515+
}
516+
} else {
517+
actualPriority = priority
518+
}
519+
520+
// Set up the job flags for a new task.
521+
var flags = Task.JobFlags()
522+
flags.kind = .task
523+
flags.priority = actualPriority
524+
flags.isFuture = true
525+
526+
// Create the asynchronous task future.
527+
let (task, _) = Builtin.createAsyncTaskFuture(flags.bits, operation)
528+
529+
// Enqueue the resulting job.
530+
_enqueueJobGlobal(Builtin.convertTaskToJob(task))
531+
}
532+
484533
// ==== Async Handler ----------------------------------------------------------
485534

486535
// TODO: remove this?
@@ -746,6 +795,10 @@ func _reportUnexpectedExecutor(_ _filenameStart: Builtin.RawPointer,
746795
_ _line: Builtin.Word,
747796
_ _executor: Builtin.Executor)
748797

798+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
799+
@_silgen_name("swift_task_getCurrentThreadPriority")
800+
func _getCurrentThreadPriority() -> Int
801+
749802
#if _runtime(_ObjC)
750803

751804
/// Intrinsic used by SILGen to launch a task for bridging a Swift async method

test/Concurrency/Runtime/async.swift

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// RUN: %target-run-simple-swift(-Xfrontend -enable-experimental-concurrency %import-libdispatch)
2+
3+
// REQUIRES: executable_test
4+
// REQUIRES: concurrency
5+
// REQUIRES: libdispatch
6+
7+
// rdar://76038845
8+
// UNSUPPORTED: use_os_stdlib
9+
10+
import Dispatch
11+
import StdlibUnittest
12+
13+
// for sleep
14+
#if canImport(Darwin)
15+
import Darwin
16+
#elseif canImport(Glibc)
17+
import Glibc
18+
#endif
19+
20+
var asyncTests = TestSuite("Async")
21+
22+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
23+
actor MyActor {
24+
func synchronous() { }
25+
26+
func doSomething(expectedPriority: Task.Priority) {
27+
async {
28+
synchronous() // okay to be synchronous
29+
assert(Task.currentPriority == expectedPriority)
30+
}
31+
}
32+
}
33+
34+
if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) {
35+
let actor = MyActor()
36+
37+
asyncTests.test("Detach") {
38+
detach(priority: .background) {
39+
async {
40+
assert(Task.currentPriority == .background)
41+
await actor.doSomething(expectedPriority: .background)
42+
}
43+
}
44+
sleep(1)
45+
}
46+
47+
asyncTests.test("MainQueue") {
48+
DispatchQueue.main.async {
49+
async {
50+
assert(Task.currentPriority == .userInitiated)
51+
}
52+
}
53+
sleep(1)
54+
}
55+
56+
asyncTests.test("GlobalDispatchQueue") {
57+
DispatchQueue.global(qos: .utility).async {
58+
async {
59+
#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS))
60+
// Non-Darwin platforms currently lack qos_class_self().
61+
assert(Task.currentPriority == .utility)
62+
#endif
63+
}
64+
}
65+
sleep(1)
66+
}
67+
}
68+
69+
runAllTests()
70+

0 commit comments

Comments
 (0)