I am trying to understand as to why following piece of code throws an assertion. What I am trying to do is to call asyncFunc() on main thread/main actor from call site. I don't want to decorate asyncFunc with #MainActor as I want the function to be thread agnostic.
func asyncFunc() async -> String? {
dispatchPrecondition(condition: .onQueue(.main))
return "abc"
}
func callSite() {
Task { #MainActor in
await asyncFunc()
}
}
My understanding was that Task { #MainActor ...} would execute all the following code on MainActor/main thread.
Currently actors in swift are reentrant as mentioned in proposal:
Actor-isolated functions are reentrant. When an actor-isolated function suspends, reentrancy allows other work to execute on the actor before the original actor-isolated function resumes, which we refer to as interleaving. Reentrancy eliminates a source of deadlocks, where two actors depend on each other, can improve overall performance by not unnecessarily blocking work on actors, and offers opportunities for better scheduling of (e.g.) higher-priority tasks.
Hence, your asyncFunc is not run on MainActor as it will block the actor from processing other high-priority tasks. To fix this you can try following two approaches:
Make your function synchronous, so that it will run on MainActor:
func syncFunc() -> String? {
dispatchPrecondition(condition: .onQueue(.main))
return "abc"
}
func callSite() {
Task { #MainActor in
syncFunc()
}
}
Or explicitly mark your asynchronous function to be run on MainActor:
#MainActor
func asyncFunc() async -> String? {
dispatchPrecondition(condition: .onQueue(.main))
return "abc"
}
func callSite() {
Task {
await asyncFunc()
}
}
Note that same reentrancy rule applies here as well, synchronous calls in asyncFunc (i.e. dispatchPrecondition) will be executed on MainActor while any asynchronous call inside asyncFunc will not be called on MainActor.
Related
I have an #MainActor function that I want to run synchronously. So far, I've come up with this:
static func runOnMainThreadAndWait<T>(
action: #MainActor #Sendable () throws -> T
) rethrows -> T {
if Thread.isMainThread {
return try unsafeExecuteWithoutActorChecking(action)
} else {
return try DispatchQueue.main.sync(execute: action)
}
}
#preconcurrency
private static func unsafeExecuteWithoutActorChecking<T>(
_ action: () throws -> T
) rethrows -> T {
return try action()
}
This works, but the first case of that if statement feels janky, and with -warn-concurrency even unsafeExecuteWithoutActorChecking(_:) doesn't suffice:
Converting function value of type '#MainActor #Sendable () throws -> T' to '() throws -> T' loses global actor 'MainActor'
What's the safest way to implement this call?
Edit, for more details:
The function is usually called from the main thread -- specifically, when a view is created -- and the return value is needed to render the views. It was originally written under the assumption that this would be the only way it was called, so no safety mechanisms were put in place. I have been going back through the code and adding #MainActor annotations and similar safety checks where appropriate. Here, I need to support the common case (calling it synchronously while on the main thread) while making the incorrect behavior (running on a background thread) impossible. I added an asynchronous overload that calls await MainActor.run ... as a stopgap measure, but most of the time awaiting it is unnecessary and only adds noise.
I've seen a variation on this solution from before Swift's structured concurrency:
static func runOnMainThreadSync<T>(action: () throws -> T) rethrows -> T {
if Thread.isMainThread {
return try action()
} else {
return try DispatchQueue.main.sync(execute: action)
}
}
and I thought I could just add #MainActor to this.
Running something in the #MainActor context does not mean that you are running on the main thread.
It does mean that your code will run synchronously relative to other code running on the Main Actor, which includes code running in the main thread. But all that means is that while your code is running in the main actor context, if it's not running on the main thread, then the main thread will be suspended. (no other main actor code will be running in parallel)
So your if check is not just janky, it's bogus.
You don't say much about the calling context where you are trying to run something on the main actor, but the easiest solution is probably to use
await MainActor.run {
... some code here ...
}
You said:
I've seen a variation on this solution from before Swift's structured concurrency:
static func runOnMainThreadSync<T>(action: () throws -> T) rethrows -> T >{
if Thread.isMainThread {
return try action()
} else {
return try DispatchQueue.main.sync(execute: action)
}
}
and I thought I could just add #MainActor to this.
You can (and simplify it significantly):
extension MainActor {
#MainActor static func runOnMainThread<T>(action: #MainActor #Sendable () throws -> T) rethrows -> T {
try action()
}
}
But I would advise against this. One could use MainActor.run.
Or, even better, we can just mark the methods that must be run on a particular actor, and the compiler will perform compile-time checks (rather than the above run-time checks). If you call it from a context not isolated to the particular actor the compiler will tell you that you have to await. And only if you have a series of await suspension points to the main actor and would like to wrap that in a single task would you ever need to use MainActor.run.
But with Swift concurrency, there is no guessing, like we might have had to do in GCD. Nowadays, this runOnMainThreadSync pattern (anti-pattern?) is unnecessary. In Swift concurrency, we are generally explicit about which methods and properties are actor isolated, eliminating the ambiguity.
With the PromiseKit library, it’s possible to create a promise and a resolver function together and store them on an instance of a class:
class ExampleClass {
// Promise and resolver for the top news headline, as obtained from
// some web service.
private let (headlinePromise, headlineSeal) = Promise<String>.pending()
}
Like any promise, we can chain off of headlinePromise to do some work once the value is available:
headlinePromise.get { headline in
updateUI(headline: headline)
}
// Some other stuff here
Since the promise has not been resolved yet, the contents of the get closure will be enqueued somewhere and control will immediately move to the “some other stuff here” section; updateUI will not be called unless and until the promise is resolved.
To resolve the promise, an instance method can call headlineSeal:
makeNetworkRequest("https://news.example/headline").get { headline in
headlineSeal.fulfill(headline)
}
The promise is now resolved, and any promise chains that had been waiting for headlinePromise will continue. For the rest of the life of this ExampleClass instance, any promise chain starting like
headlinePromise.get { headline in
// ...
}
will immediately begin executing. (“Immediately” might mean “right now, synchronously,” or it might mean “on the next run of the event loop”; the distinction isn’t important for me here.) Since promises can only be resolved once, any future calls to headlineSeal.fulfill(_:) or headlineSeal.reject(_:) will be no-ops.
Question
How can this pattern be translated idiomatically into Swift concurrency (“async/await”)? It’s not important that there be an object called a “promise” and a function called a “resolver”; what I’m looking for is a setup that has the following properties:
It’s possible for some code to declare a dependency on some bit of asynchronously-available state, and yield until that state is available.
It’s possible for that state to be “fulfilled” from potentially any instance method.
Once the state is available, any future chains of code that depend on that state are able to run right away.
Once the state is available, its value is immutable; the state cannot become unavailable again, nor can its value be changed.
I think that some of these can be accomplished by storing an instance variable
private let headlineTask: Task<String, Error>
and then waiting for the value with
let headline = try await headlineTask.value
but I’m not sure how that Task should be initialized or how it should be “fulfilled.”
Here is a way to reproduce a Promise which can be awaited by multiple consumers and fulfilled by any synchronous code:
public final class Promise<Success: Sendable>: Sendable {
typealias Waiter = CheckedContinuation<Success, Never>
struct State {
var waiters = [Waiter]()
var result: Success? = nil
}
private let state = ManagedCriticalState(State())
public init(_ elementType: Success.Type = Success.self) { }
#discardableResult
public func fulfill(with value: Success) -> Bool {
return state.withCriticalRegion { state in
if state.result == nil {
state.result = value
for waiters in state.waiters {
waiters.resume(returning: value)
}
state.waiters.removeAll()
return false
}
return true
}
}
public var value: Success {
get async {
await withCheckedContinuation { continuation in
state.withCriticalRegion { state in
if let result = state.result {
continuation.resume(returning: result)
} else {
state.waiters.append(continuation)
}
}
}
}
}
}
extension Promise where Success == Void {
func fulfill() -> Bool {
return fulfill(with: ())
}
}
The ManagedCriticalState type can be found in this file from the SwiftAsyncAlgorithms package.
I think I got the implementation safe and correct but if someone finds an error I'll update the answer. For reference I got inspired by AsyncChannel and this blog post.
You can use it like this:
#main
enum App {
static func main() async throws {
let promise = Promise(String.self)
// Delayed fulfilling.
let fulfiller = Task.detached {
print("Starting to wait...")
try await Task.sleep(nanoseconds: 2_000_000_000)
print("Promise fulfilled")
promise.fulfill(with: "Done!")
}
let consumer = Task.detached {
await (print("Promise resolved to '\(promise.value)'"))
}
// Launch concurrent consumer and producer
// and wait for them to complete.
try await fulfiller.value
await consumer.value
// A promise can be fulfilled only once and
// subsequent calls to `.value` immediatly return
// with the previously resolved value.
promise.fulfill(with: "Ooops")
await (print("Promise still resolved to '\(promise.value)'"))
}
}
Short explanation
In Swift Concurrency, the high-level Task type resembles a Future/Promise (it can be awaited and suspends execution until resolved) but the actual resolution cannot be controlled from the outside: one must compose built-in lower-level asynchronous functions such as URLSession.data() or Task.sleep().
However, Swift Concurrency provides a (Checked|Unsafe)Continuation type which basically act as a Promise resolver. It is a low-lever building block which purpose is to migrate regular asynchronous code (callback-based for instance) to the Swift Concurrency world.
In the above code, continuations are created by the consumers (via the .value property) and stored in the Promise. Later, when the result is available the stored continuations are fulfilled (with .resume()), which resumes the execution of the consumers. The result is also cached so that if it is already available when .value is called it is directly returned to the called.
When a Promise is fulfilled multiple times, the current behavior is to ignore subsequent calls and to return aa boolean value indicating if the Promise was already fulfilled. Other API's could be used (a trap, throwing an error, etc.).
The internal mutable state of the Promise must be protected from concurrent accesses since multiple concurrency domains could try to read and write from it at the same time. This is achieve with regular locking (I believe this could have been achieved with an actor, though).
I don't understand what's the difference between add(_) and add(_) async method. Like the below code, the MyActor has two add methods and one of them uses async keyword. They can exist at the same time. If I comment out the second add method it will output AAAA. If both add methods exist at the same time, output "BBBBB"。
actor MyActor {
var num: Int = 0
func add(_ value: Int) {
print("AAAAA")
num += value
}
func add(_ value: Int) async {
print("BBBBB")
num += value
}
}
let actor = MyActor()
Task {
await actor.add(200)
print(await actor.num)
}
Supplementary:
With the second add method commented out, I defined let actor = MyActor() outside Task and I noticed the add method signed as add(_:). If move let actor = MyActor() inside Task the add method signed as add(_:) async
The difference emerges inside the actor, for example
actor MyActor {
func addSync(_ value: Int) {
}
func addAsync(_ value: Int) async {
}
func f() {
addSync(0)
}
func g() async {
addSync(0)
await addAsync(0)
}
}
In the actor method f and g you can call addSync synchronously. While outside the actor, you need always call an actor method with the await keyword as if the method is asynchronous:
func outside() async {
let actor = MyActor()
await actor.addSync(0)
}
Async in Swift allows for structured concurrency, which will improve the readability of complex asynchronous code. Completion closures are no longer needed, and calling into multiple asynchronous methods after each other is a lot more readable.
Async stands for asynchronous and can be seen as a method attribute making it clear that a method performs asynchronous work. An example of such a method looks as follows:
func fetchImages() async throws -> [UIImage] {
// .. perform data request
}
The fetchImages method is defined as async throwing, which means that it’s performing a failable asynchronous job. The method would return a collection of images if everything went well or throws an error if something went wrong.
How async replaces closure completion callbacks
Async methods replace the often seen closure completion callbacks. Completion callbacks were common in Swift to return from an asynchronous task, often combined with a Result type parameter. The above method would have been written as followed:
func fetchImages(completion: (Result<[UIImage], Error>) -> Void) {
// .. perform data request
}
Defining a method using a completion closure is still possible in Swift today, but it has a few downsides that are solved by using async instead:
You have to make sure yourself to call the completion closure in each possible method exit. Not doing so will possibly result in an app waiting for a result endlessly.
Closures are harder to read. It’s not as easy to reason about the order of execution as compared to how easy it is with structured concurrency.
Retain cycles need to be avoided using weak references.
Implementors need to switch over the result to get the outcome. It’s not possible to use try catch statements from the implementation level.
These downsides are based on the closure version using the relatively new Result enum. It’s likely that a lot of projects still make use of completion callbacks without this enumeration:
func fetchImages(completion: ([UIImage]?, Error?) -> Void) {
// .. perform data request
}
Defining a method like this makes it even harder to reason about the outcome on the caller’s side. Both value and error are optional, which requires us to perform an unwrap in any case. Unwrapping these optionals results in more code clutter which does not help to improve readability.
I'm bridging the sync/async worlds in Swift and doing incremental adoption of async/await. I'm trying to invoke an async function that returns a value from a non async function. I understand that explicit use of Task is the way to do that, as described, for instance, here.
The example doesn't really fit as that task doesn't return a value.
After much searching, I haven't been able to find any description of what I'd think was a pretty common ask: synchronous invocation of an asynchronous task (and yes, I understand that that can freeze up the main thread).
What I theoretically would like to write in my synchronous function is this:
let x = Task {
return await someAsyncFunction()
}.result
However, when I try to do that, I get this compiler error due to trying to access result:
'async' property access in a function that does not support concurrency
One alternative I found was something like:
Task.init {
self.myResult = await someAsyncFunction()
}
where myResult has to be attributed as a #State member variable.
However, that doesn't work the way I want it to, because there's no guarantee of completing that task prior to Task.init() completing and moving onto the next statement. So how can I wait synchronously for that Task to be complete?
You should not wait synchronously for an async task.
One may come up with a solution similar to this:
func updateDatabase(_ asyncUpdateDatabase: #Sendable #escaping () async -> Void) {
let semaphore = DispatchSemaphore(value: 0)
Task {
await asyncUpdateDatabase()
semaphore.signal()
}
semaphore.wait()
}
Although it works in some simple conditions, according to WWDC 2021 Swift Concurrency: Behind the scenes, this is unsafe. The reason is the system expects you to conform to a runtime contract. The contract requires that
Threads are always able to make forward progress.
That means threads are never blocking. When an asynchronous function reaches a suspension point (e.g. an await expression), the function can be suspended, but the thread does not block, it can do other works. Based on this contract, the new cooperative thread pool is able to only spawn as many threads as there are CPU cores, avoiding excessive thread context switches. This contract is also the key reason why actors won't cause deadlocks.
The above semaphore pattern violates this contract. The semaphore.wait() function blocks the thread. This can cause problems. For example
func testGroup() {
Task {
await withTaskGroup(of: Void.self) { group in
for _ in 0 ..< 100 {
group.addTask {
syncFunc()
}
}
}
NSLog("Complete")
}
}
func syncFunc() {
let semaphore = DispatchSemaphore(value: 0)
Task {
try? await Task.sleep(nanoseconds: 1_000_000_000)
semaphore.signal()
}
semaphore.wait()
}
Here we add 100 concurrent child tasks in the testGroup function, unfortunately the task group will never complete. In my Mac, the system spawns 4 cooperative threads, adding only 4 child tasks is enough to block all 4 threads indefinitely. Because after all 4 threads are blocked by the wait function, there is no more thread available to execute the inner task that signals the semaphore.
Another example of unsafe use is actor deadlock:
func testActor() {
Task {
let d = Database()
await d.updateSettings()
NSLog("Complete")
}
}
func updateDatabase(_ asyncUpdateDatabase: #Sendable #escaping () async -> Void) {
let semaphore = DispatchSemaphore(value: 0)
Task {
await asyncUpdateDatabase()
semaphore.signal()
}
semaphore.wait()
}
actor Database {
func updateSettings() {
updateDatabase {
await self.updateUser()
}
}
func updateUser() {
}
}
Here calling the updateSettings function will deadlock. Because it waits synchronously for the updateUser function, while the updateUser function is isolated to the same actor, so it waits for updateSettings to complete first.
The above two examples use DispatchSemaphore. Using NSCondition in a similar way is unsafe for the same reason. Basically waiting synchronously means blocking the current thread. Avoid this pattern unless you only want a temporary solution and fully understand the risks.
Other than using semaphore, you can wrap your asynchronous task inside an operation like here. You can signal the operation finish once the underlying async task finishes and wait for operation completion using waitUntilFinished():
let op = TaskOperation {
try await Task.sleep(nanoseconds: 1_000_000_000)
}
op.waitUntilFinished()
Note that using semaphore.wait() or op.waitUntilFinished() blocks the current thread and blocking the thread can cause undefined runtime behaviors in modern concurrency as modern concurrency assumes all threads are always making forward progress. If you are planning to use this method only in contexts where you are not using modern concurrency, with Swift 5.7 you can provide attribute mark method unavailable in asynchronous context:
#available(*, noasync, message: "this method blocks thread use the async version instead")
func yourBlockingFunc() {
// do work that can block thread
}
By using this attribute you can only invoke this method from a non-async context. But some caution is needed as you can invoke non-async methods that call this method from an async context if that method doesn't specify noasync availability.
I wrote simple functions that can run asynchronous code as synchronous similar as Kotlin does, you can see code here. It's only for test purposes, though. DO NOT USE IT IN PRODUCTION as async code must be run only asynchronous
Example:
let result = runBlocking {
try? await Task.sleep(nanoseconds: 1_000_000_000)
return "Some result"
}
print(result) // prints "Some result"
I've been wondering about this too. How can you start a Task (or several) and wait for them to be done in your main thread, for example? This may be C++ like thinking but there must be a way to do it in Swift as well. For better or worse, I came up with using a global variable to check if the work is done:
import Foundation
var isDone = false
func printIt() async {
try! await Task.sleep(nanoseconds: 200000000)
print("hello world")
isDone = true
}
Task {
await printIt()
}
while !isDone {
Thread.sleep(forTimeInterval: 0.1)
}
I have a function that is a async wrapper around a synchronous function.
The synchronous function is like:
class Foo {
class func bar() -> [Int] {
return [1,2,3]
}
class func asyncBar(completion: #escaping ([Int]) -> Void) {
DispatchQueue.global(qos: .userInitiated).async {
let intArray = bar()
DispatchQueue.main.async {
completion(intArray)
}
}
}
}
When I call it from a XCTestCase completion does not run.
Is there some sort of perverse interaction between the way unit tests are done in XCode and the main thread?
I can find no documentation on the Web about this.
I have to use the main thread for the callback as it interacts with the Gui.
My test case looks something like:
func testAsyncBar() throws {
var run = true
func stopThisThing(ints: [Int]) {
run = false
}
Foo.asyncBar(completion: stopThisThing)
while run {
print ("Running...")
usleep(100000)
}
}
The busy loop at the end never stops.
Your test’s while loop will block the main thread (if the test runs on the main thread). As such, the closure dispatched via DispatchQueue.main.async can never run, and your run Boolean will never get reset. This results in a deadlock. You can confirm this by printing Thread.isMainThread or adding a test like dispatchPrecondition(condition: .onQueue(.main)).
Fortunately, unit tests have a simple mechanism that avoids this deadlock.
If you want unit test to wait for some asynchronous process, use an XCTestExpectation:
func testAsyncBar() throws {
let e = expectation(description: "asyncBar")
func stopThisThing(ints: [Int]) {
e.fulfill()
}
Foo.asyncBar(completion: stopThisThing)
waitForExpectations(timeout: 5)
}
This avoids problems introduced by the while loop that otherwise blocked the thread.
See Testing Asynchronous Operations with Expectations.