Swift How to wait until a specific condition is met - swift

I've looked through just about every question on this topic I could find but I've had little success. I need to run a function on an array of actors conforming to a specific actor protocol. Because these are actors, I need an async call. But I also need to run these functions in a specific order, and I'm not going to describe how I get the order, just suffice it to say that I have it. I am also using the following asyncForEach function, though I'm open to not doing this.
extension Sequence {
func asyncForEach (
_ operation: #escaping (Element) async -> Void
) async {
// A task group automatically waits for all of its
// sub-tasks to complete, while also performing those
// tasks in parallel:
await withTaskGroup(of: Void.self) { group in
for element in self {
group.addTask {
await operation(element)
}
}
}
}
}
Now I have some protocol
protocol ProtocolConformingActors: Actor {
func bar() async throws
}
This leads me to running my function
func foo() async throws {
let actorsAndOrders: [Int: ProtocolConformingActors] = [1:actor1, 2:actor2, 3:actor3]
// Get order
var orders: [Int] = []
for entry in actorsAndOrders {
orders.append(entry.key)
}
orders.sort()
// Run func
await orders.asyncForEach { order in
let actor = actorsAndOrders[order]
try await actor?.bar()
}
}
And this is where the problem occurs. Like I mentioned above, these calls need to be async because bar() is modifying isolated properties on each actor. Because in order to make this happen, I need to use the asyncForEach, but as I understand it, the asyncForEach loop sets up and runs each function bar() in parallel. But they need to be run in order.
Is there a way I can make each thread wait until a condition is met?
I was thinking I might be able to use the condition orders[0] == order and when bar() is done running, remove the first entry from the orders array, which could make it tell the next thread it can wake up again.
But while the apple documentation seems to indicate that there is a wait(until:) function on NSCondition, I can't seem to make it work.

Related

What is the Swift concurrency equivalent to a promise–resolver pair?

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).

Swift: withCheckedContinuation and Dispatch QoSClass

I am adapting some old code which was using common completions in order to use the new async/await syntax with Parse SDK. Here is an example, this:
static func get(
className: String,
id: String,
_ completion: #escaping (PFObject?) -> Void
) {
let query = PFQuery(className: className)
query.getObjectInBackground(withId: id) { object, _ in
completion(object)
}
}
is becoming this:
static func get(
className: String,
objectId: String
) async -> PFObject? {
let query = PFQuery(className: className)
return await withCheckedContinuation { continuation in
query.getObjectInBackground(withId: objectId) { result, _ in
continuation.resume(returning: result)
}
}
}
However, I was also using DispatchQueue/QoS previously and so the old function actually looked like this:
static func get(
className: String,
id: String,
_ completion: #escaping (PFObject?) -> Void
) {
let query = PFQuery(className: className)
DispatchQueue.global(qos: .userInteractive).async {
query.getObjectInBackground(withId: id) { object, _ in
DispatchQueue.main.async {
completion(object)
}
}
}
}
How can I use this with the async/await syntax? Is it even needed?
Thank you for your help
You probably want to execute your async function get(className:objectId:) within a Swift Task, using this Task initialiser:
func foo(
className: String,
objectId: String
) async -> PFObject? {
await Task(priority: .userInitiated) {
await get(className: className, objectId: objectId)
}
.value
}
Note that, when using this Task initialiser you are getting "structured concurrency", which means, that the embedded task in function foo inherits the actor context of the calling function.
That is, you can use the result safely from whatever thread you called it.
That means also, if the task where function foo() is running, gets cancelled, the embedded task will be cancelled as well. Of course, cancellation is cooperatively, which means, you need to stop a running task explicitly. For example, as preferred in your use case, with a withTaskCancellationHandler, which calls cancel() on your PFQuery object. Or when you have a long running iterating task, you may poll the Task's cancellation status in reasonably steps while your task progresses.
Please also read about "detached" which behave differently regarding cancellation and inheriting the task priority.
As of your question whether it is needed:
I assume, you ask if using specifying a priority is needed:
Short answer: in many use cases it may be more safe to just inherit the priority from whatever thread the function originates.
In your case, I would not explicitly change it: when it is called from a background thread with low priority, your task should inheriting this priority as well. There will be no reason to make it high priority with userInitiated. Otherwise, if your function is already called from a user action, it will be inheriting this higher priority as well. I believe, this is what most use cases would require.
Also, when your worker is running in a background thread, as is the case in your code, it really doesn't matter much "how fast you schedule" this task.
So, basically you end up with your async get function as defined, use it as is, and all is good. ;)

What's difference between `add(_)` and `add(_) async`?

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.

Swift - ThreadSanitizer complaining of data race

I am running ThreadSanitizer on my project and I am getting some very inconsistent results. My setup is as follows:
A wrapper around a URLSession which reports back tasks and their status. I get the tasks via:
actor MyMonitor {
func getTasks() async -> [String: URLSessionTask] {
await mySession.allTasks.reduce(into: [String: URLSessionTask]()) { newMap, element in
if let taskDescription = element.taskDescription {
newMap[taskDescription] = element
}
}
}
}
For my unit tests, I have a URLProtocol to mock an actual working request. However, it seems when I call the getTasks function, I get a data race. (albeit inconsistent)
The TSan tool states the race is in the above snippet:
Threading Issues: Data race in (2) await resume partial function for
MyProject.MyMonitor.getTasks() async -> Swift.Dictionary<Swift.String,
__C.NSURLSessionTask> at 0x7b6000021400
Is this a false positive, because I do not have control ove URLSession's threading? Any help would be appreciated.

swift calling async function without a return value

Is there anyway in swift's new structured concurrency model to do the following without a dummy bool return?
func do() async -> Bool {
something()
return true
}
async let foo = do()
//do other stuff
stuff()
//now I need to know that "do" has finished
await foo
I know that I can do the following but it will not run concurrently:
func do() async {
something()
}
await do()
stuff()
//cannot run "stuff" and "do" concurrently
I feel like I am missing a basic idea here because the top block of code does what I need but feels like a hack due to the Bool return.
What you're describing is a Task. For example:
Task { await `do`() }
stuff()
This will run do() concurrently with stuff(). If you need to keep track of when do() completes, you can await the task's value:
let task = Task { await `do`() }
stuff()
await task.value // Doesn't actually return anything, but will block
This kind of Task runs in the context of the current Actor, which is usually what you want. If you want something independent of the current Actor, you can use Task.detached() instead.
If you've previously used DispatchQueues, in many of the places you would have written queue.async { ... }, you can now write Task { ... }. The new system is much more powerful, but it maps fairly nicely to the old system if you want it to.
Swift implicitly returns Void for non-returning function, so I guess this would be fine
func do() async {
something()
}
async let foo: Void = do() // just explicit Void so the compiler doesn't emit a warning telling you that may not be expected
//do other stuff
stuff()
//now I need to know that "do" has finished
await foo