Run function after two functions has ran - swift

So let's say I have these three functions:
func func_1() {
Task { #MainActor in
let state = try await api.get1State(v!)
print("cState func_1: \(state!)")
}
}
func func_2() {
Task { #MainActor in
let state = try await api.get2State(v!)
print("cState func_2: \(state!)")
}
}
func func_3() {
Task { #MainActor in
let state = try await api.get3State(v!)
print("cState func_3: \(state!)")
}
}
Since these function get info from api, it might take a few seconds.
How can I run func_3, after both func_1 and func_2 is done running?

I would advise avoiding Task (which opts out of structured concurrency, and loses all the benefits that entails) unless you absolutely have to. E.g., I generally try to limit Task to those cases where I are going from a non-asynchronous context to an asynchronous one. Where possible, I try to stay within structured concurrency.
As The Swift Programming Language: Concurrency says:
Tasks are arranged in a hierarchy. Each task in a task group has the same parent task, and each task can have child tasks. Because of the explicit relationship between tasks and task groups, this approach is called structured concurrency. Although you take on some of the responsibility for correctness, the explicit parent-child relationships between tasks lets Swift handle some behaviors like propagating cancellation for you, and lets Swift detect some errors at compile time.
And I would avoid creating functions (func_1, func_2, and func_3) that fetch a value and throw it away. You would presumably return the values.
If func_1 and func_2 return different types, you could use async let. E.g., if you're not running func_3 until the first two are done, perhaps it uses those values as inputs:
func runAll() async throws {
async let foo = try await func_1()
async let bar = try await func_2()
let baz = try await func_3(foo: foo, bar: bar)
}
func func_1() async throws -> Foo {
let foo = try await api.get1State(v!)
print("cState func_1: \(foo)")
return foo
}
func func_2() async throws -> Bar {
let bar = try await api.get2State(v!)
print("cState func_2: \(bar)")
return bar
}
func func_3(foo: Foo, bar: Bar) async throws -> Baz {
let baz = try await api.get3State(foo, bar)
print("cState func_3: \(baz)")
return baz
}
Representing that visually using “Points of Interest” tool in Instruments:
The other pattern, if func_1 and func_2 return the same type, is to use a task group:
func runAll() async throws {
let results = try await withThrowingTaskGroup(of: Foo.self) { group in
group.addTask { try await func_1() }
group.addTask { try await func_2() }
return try await group.reduce(into: [Foo]()) { $0.append($1) } // note, this will be in the order that they complete; we often use a dictionary instead
}
let baz = try await func_3(results)
}
func func_1() async throws -> Foo { ... }
func func_2() async throws -> Foo { ... }
func func_3(_ values: [Foo]) async throws -> Baz { ... }
There are lots of permutations of the pattern, so do not get lost in the details here. The basic idea is that (a) you want to stay within structured concurrency; and (b) use async let or TaskGroup for those tasks you want to run in parallel.
I hate to mention it, but for the sake of completeness, you can used Task and unstructured concurrency. From the same document I referenced above:
Unstructured Concurrency
In addition to the structured approaches to concurrency described in the previous sections, Swift also supports unstructured concurrency. Unlike tasks that are part of a task group, an unstructured task doesn’t have a parent task. You have complete flexibility to manage unstructured tasks in whatever way your program needs, but you’re also completely responsible for their correctness.
I would avoid this because you need to handle/capture the errors manually and is somewhat brittle, but you can return the Task objects, and await their respective result:
func func_1() -> Task<(), Error> {
Task { #MainActor [v] in
let state = try await api.get1State(v!)
print("cState func_1: \(state)")
}
}
func func_2() -> Task<(), Error> {
Task { #MainActor [v] in
let state = try await api.get2State(v!)
print("cState func_2: \(state)")
}
}
func func_3() -> Task<(), Error> {
Task { #MainActor [v] in
let state = try await api.get3State(v!)
print("cState func_3: \(state)")
}
}
func runAll() async throws {
let task1 = func_1()
let task2 = func_2()
let _ = await task1.result
let _ = await task2.result
let _ = await func_3().result
}
Note, I did not just await func_1().result directly, because you want the first two tasks to run concurrently. So launch those two tasks, save the Task objects, and then await their respective result before launching the third task.
But, again, your future self will probably thank you if you remain within the realm of structured concurrency.

Related

Swift Concurrency async/await equivalent of a dispatch barrier / semaphore [duplicate]

I've a document based application that uses a struct for its main data/model. As the model is a property of (a subclass of) NSDocument it needs to be accessed from the main thread. So far all good.
But some operations on the data can take quite a long time and I want to provide the user with a progress bar. And this is where to problems start. Especially when the user starts two operations from the GUI in quick succession.
If I run the operation on the model synchronously (or in a 'normal' Task {}) I get the correct serial behaviour, but the Main thread is blocked, hence I can't show a progress bar. (Option A)
If I run the operation on the model in a Task.detached {} closure I can update the progress bar, but depending on the run time of the operations on the model, the second action of the user might complete before the first operation, resulting in invalid/unexpected state of the model. This is due to the await statements needed in the detached task (I think). (Option B).
So I want a) to free up the main thread to update the GUI and b) make sure each task runs to full completion before another (queued) task starts. This would be quite possible using a background serial dispatch queue, but I'm trying to switch to the new Swift concurrency system, which is also used to perform any preparations before the model is accessed.
I tried using a global actor, as that seems to be some sort of serial background queue, but it also needs await statements. Although the likelihood of unexpected state in the model is reduced, it's still possible.
I've written some small code to demonstrate the problem:
The model:
struct Model {
var doneA = false
var doneB = false
mutating func updateA() {
Thread.sleep(forTimeInterval: 5)
doneA = true
}
mutating func updateB() {
Thread.sleep(forTimeInterval: 1)
doneB = true
}
}
And the document (leaving out standard NSDocument overrides):
#globalActor
struct ModelActor {
actor ActorType { }
static let shared: ActorType = ActorType()
}
class Document: NSDocument {
var model = Model() {
didSet {
Swift.print(model)
}
}
func update(model: Model) {
self.model = model
}
#ModelActor
func updateModel(with operation: (Model) -> Model) async {
var model = await self.model
model = operation(model)
await update(model: model)
}
#IBAction func operationA(_ sender: Any?) {
//Option A
// Task {
// Swift.print("Performing some A work...")
// self.model.updateA()
// }
//Option B
// Task.detached {
// Swift.print("Performing some A work...")
// var model = await self.model
// model.updateA()
// await self.update(model: model)
// }
//Option C
Task.detached {
Swift.print("Performing some A work...")
await self.updateModel { model in
var model = model
model.updateA()
return model
}
}
}
#IBAction func operationB(_ sender: Any?) {
//Option A
// Task {
// Swift.print("Performing some B work...")
// self.model.updateB()
// }
//Option B
// Task.detached {
// Swift.print("Performing some B work...")
// var model = await self.model
// model.updateB()
// await self.update(model: model)
// }
//Option C
Task.detached {
Swift.print("Performing some B work...")
await self.updateModel { model in
var model = model
model.updateB()
return model
}
}
}
}
Clicking 'Operation A' and then 'Operation B' should result in a model with two true's. But it doesn't always.
Is there a way to make sure that operation A completes before I get to operation B and have the Main thread available for GUI updates?
EDIT
Based on Rob's answer I came up with the following. I modified it this way because I can then wait on the created operation and report any error to the original caller. I thought it easier to comprehend what's happening by including all code inside a single update function, so I choose to go for a detached task instead of an actor. I also return the intermediate model from the task, as otherwise an old model might be used.
class Document {
func updateModel(operation: #escaping (Model) throws -> Model) async throws {
//Update the model in the background
let modelTask = Task.detached { [previousTask, model] () throws -> Model in
var model = model
//Check whether we're cancelled
try Task.checkCancellation()
//Check whether we need to wait on earlier task(s)
if let previousTask = previousTask {
//If the preceding task succeeds we use its model
do {
model = try await previousTask.value
} catch {
throw CancellationError()
}
}
return try operation(model)
}
previousTask = modelTask
defer { previousTask = nil } //Make sure a later task can always start if we throw
//Wait for the operation to finish and store the model
do {
self.model = try await modelTask.value
} catch {
if error is CancellationError { return }
else { throw error }
}
}
}
Call side:
#IBAction func operationA(_ sender: Any?) {
//Option D
Task {
do {
try await updateModel { model in
var model = model
model.updateA()
return model
}
} catch {
presentError(error)
}
}
}
It seems to do anything I need, which is queue'ing updates to a property on a document, which can be awaited for and have errors returned, much like if everything happened on the main thread.
The only drawback seems to be that on the call side the closure is very verbose due to the need to make the model a var and return it explicitly.
Obviously if your tasks do not have any await or other suspension points, you would just use an actor, and not make the method async, and it automatically will perform them sequentially.
But, when dealing with asynchronous actor methods, one must appreciate that actors are reentrant (see SE-0306: Actors - Actor Reentrancy). If you really are trying to a series of asynchronous tasks run serially, you will want to manually have each subsequent task await the prior one. E.g.,
actor Foo {
private var previousTask: Task<(), Error>?
func add(block: #Sendable #escaping () async throws -> Void) {
previousTask = Task { [previousTask] in
let _ = await previousTask?.result
return try await block()
}
}
}
There are two subtle aspects to the above:
I use the capture list of [previousTask] to make sure to get a copy of the prior task.
I perform await previousTask?.value inside the new task, not before it.
If you await prior to creating the new task, you have race, where if you launch three tasks, both the second and the third will await the first task, i.e. the third task is not awaiting the second one.
And, perhaps needless to say, because this is within an actor, it avoids the need for detached task, while keeping the main thread free.

How to resume a continuation ensuring that the result is delivered on the MainActor?

I have a continuation:
func a() async -> Int {
await withCheckedContinuation { continuation in
continuation.resume(returning: 3)
}
}
I would like all callers of this function to receive the result on the MainActor. I wouldn't like the caller to have to explicitly specify this rescheduling. I don't want this:
func c() async {
let three = await a()
await MainActor.run {
b(three)
}
}
What I instead want is for the entire code after returning to be performed on the MainThread until the next suspension point, something like this:
func c1() async {
let three = await a()
b(three) // Guaranteed main thread, although nothing speaks of it here
}
In a way, I want a to declare that I return only on main actor!, like this:
func a() #MainActor async -> Int {
await withCheckedContinuation { continuation in
continuation.resume(returning: 3)
}
}
Is there any way to even do this?
UPDATE:
Both commenters have suggested that I annotate the enclosing functions c and c1 with #MainActor.
#MainActor
func c() async {
let three = await a()
await MainActor.run {
b(three)
}
}
This doesn't do it like I need it. It says:
every time I await somebody, they must return on the main thread
But what I need instead is this:
every time somebody awaits me, they must get my result on the main thread
No, there is no way to do this.
If you await some function, you can decide on which thread will it return.
But being an await-able function, you can not make sure that your result will be delivered to the caller on a particular and/or main thread.

Make tasks in Swift concurrency run serially

I've a document based application that uses a struct for its main data/model. As the model is a property of (a subclass of) NSDocument it needs to be accessed from the main thread. So far all good.
But some operations on the data can take quite a long time and I want to provide the user with a progress bar. And this is where to problems start. Especially when the user starts two operations from the GUI in quick succession.
If I run the operation on the model synchronously (or in a 'normal' Task {}) I get the correct serial behaviour, but the Main thread is blocked, hence I can't show a progress bar. (Option A)
If I run the operation on the model in a Task.detached {} closure I can update the progress bar, but depending on the run time of the operations on the model, the second action of the user might complete before the first operation, resulting in invalid/unexpected state of the model. This is due to the await statements needed in the detached task (I think). (Option B).
So I want a) to free up the main thread to update the GUI and b) make sure each task runs to full completion before another (queued) task starts. This would be quite possible using a background serial dispatch queue, but I'm trying to switch to the new Swift concurrency system, which is also used to perform any preparations before the model is accessed.
I tried using a global actor, as that seems to be some sort of serial background queue, but it also needs await statements. Although the likelihood of unexpected state in the model is reduced, it's still possible.
I've written some small code to demonstrate the problem:
The model:
struct Model {
var doneA = false
var doneB = false
mutating func updateA() {
Thread.sleep(forTimeInterval: 5)
doneA = true
}
mutating func updateB() {
Thread.sleep(forTimeInterval: 1)
doneB = true
}
}
And the document (leaving out standard NSDocument overrides):
#globalActor
struct ModelActor {
actor ActorType { }
static let shared: ActorType = ActorType()
}
class Document: NSDocument {
var model = Model() {
didSet {
Swift.print(model)
}
}
func update(model: Model) {
self.model = model
}
#ModelActor
func updateModel(with operation: (Model) -> Model) async {
var model = await self.model
model = operation(model)
await update(model: model)
}
#IBAction func operationA(_ sender: Any?) {
//Option A
// Task {
// Swift.print("Performing some A work...")
// self.model.updateA()
// }
//Option B
// Task.detached {
// Swift.print("Performing some A work...")
// var model = await self.model
// model.updateA()
// await self.update(model: model)
// }
//Option C
Task.detached {
Swift.print("Performing some A work...")
await self.updateModel { model in
var model = model
model.updateA()
return model
}
}
}
#IBAction func operationB(_ sender: Any?) {
//Option A
// Task {
// Swift.print("Performing some B work...")
// self.model.updateB()
// }
//Option B
// Task.detached {
// Swift.print("Performing some B work...")
// var model = await self.model
// model.updateB()
// await self.update(model: model)
// }
//Option C
Task.detached {
Swift.print("Performing some B work...")
await self.updateModel { model in
var model = model
model.updateB()
return model
}
}
}
}
Clicking 'Operation A' and then 'Operation B' should result in a model with two true's. But it doesn't always.
Is there a way to make sure that operation A completes before I get to operation B and have the Main thread available for GUI updates?
EDIT
Based on Rob's answer I came up with the following. I modified it this way because I can then wait on the created operation and report any error to the original caller. I thought it easier to comprehend what's happening by including all code inside a single update function, so I choose to go for a detached task instead of an actor. I also return the intermediate model from the task, as otherwise an old model might be used.
class Document {
func updateModel(operation: #escaping (Model) throws -> Model) async throws {
//Update the model in the background
let modelTask = Task.detached { [previousTask, model] () throws -> Model in
var model = model
//Check whether we're cancelled
try Task.checkCancellation()
//Check whether we need to wait on earlier task(s)
if let previousTask = previousTask {
//If the preceding task succeeds we use its model
do {
model = try await previousTask.value
} catch {
throw CancellationError()
}
}
return try operation(model)
}
previousTask = modelTask
defer { previousTask = nil } //Make sure a later task can always start if we throw
//Wait for the operation to finish and store the model
do {
self.model = try await modelTask.value
} catch {
if error is CancellationError { return }
else { throw error }
}
}
}
Call side:
#IBAction func operationA(_ sender: Any?) {
//Option D
Task {
do {
try await updateModel { model in
var model = model
model.updateA()
return model
}
} catch {
presentError(error)
}
}
}
It seems to do anything I need, which is queue'ing updates to a property on a document, which can be awaited for and have errors returned, much like if everything happened on the main thread.
The only drawback seems to be that on the call side the closure is very verbose due to the need to make the model a var and return it explicitly.
Obviously if your tasks do not have any await or other suspension points, you would just use an actor, and not make the method async, and it automatically will perform them sequentially.
But, when dealing with asynchronous actor methods, one must appreciate that actors are reentrant (see SE-0306: Actors - Actor Reentrancy). If you really are trying to a series of asynchronous tasks run serially, you will want to manually have each subsequent task await the prior one. E.g.,
actor Foo {
private var previousTask: Task<(), Error>?
func add(block: #Sendable #escaping () async throws -> Void) {
previousTask = Task { [previousTask] in
let _ = await previousTask?.result
return try await block()
}
}
}
There are two subtle aspects to the above:
I use the capture list of [previousTask] to make sure to get a copy of the prior task.
I perform await previousTask?.value inside the new task, not before it.
If you await prior to creating the new task, you have race, where if you launch three tasks, both the second and the third will await the first task, i.e. the third task is not awaiting the second one.
And, perhaps needless to say, because this is within an actor, it avoids the need for detached task, while keeping the main thread free.

What is the correct way to await the completion of two Tasks in Swift 5.5 in a function that does not support concurrency?

I have an app that does some processing given a string, this is done in 2 Tasks. During this time i'm displaying an animation. When these Tasks complete i need to hide the animation. The below code works, but is not very nice to look at. I believe there is a better way to do this?
let firTask = Task {
/* Slow-running code */
}
let airportTask = Task {
/* Even more slow-running code */
}
Task {
_ = await firTask.result
_ = await airportTask.result
self.isVerifyingRoute = false
}
Isn't the real problem that this is a misuse of Task? A Task, as you've discovered, is not really of itself a thing you can await. If the goal is to run slow code in the background, use an actor. Then you can cleanly call an actor method with await.
let myActor = MyActor()
await myActor.doFirStuff()
await myActor.doAirportStuff()
self.isVerifyingRoute = false
However, we also need to make sure we're on the main thread when we talk to self — something that your code omits to do. Here's an example:
actor MyActor {
func doFirStuff() async {
print("starting", #function)
await Task.sleep(2 * 1_000_000_000)
print("finished", #function)
}
func doAirportStuff() async {
print("starting", #function)
await Task.sleep(2 * 1_000_000_000)
print("finished", #function)
}
}
func test() {
let myActor = MyActor()
Task {
await myActor.doFirStuff()
await myActor.doAirportStuff()
Task { #MainActor in
self.isVerifyingRoute = false
}
}
}
Everything happens in the right mode: the time-consuming stuff happens on background threads, and the call to self happens on the main thread. A cleaner-looking way to take care of the main thread call, in my opinion, would be to have a #MainActor method:
func test() {
let myActor = MyActor()
Task {
await myActor.doFirStuff()
await myActor.doAirportStuff()
self.finish()
}
}
#MainActor func finish() {
self.isVerifyingRoute = false
}
I regard that as elegant and clear.
I would make the tasks discardable with an extension. Perhaps something like this:
extension Task {
#discardableResult
func finish() async -> Result<Success, Failure> {
await self.result
}
}
Then you could change your loading task to:
Task {
defer { self.isVerifyingRoute = false }
await firTask.finish()
await airportTask.finish()
}

'async' call in a function that does not support concurrency

I'm trying to use async/await with Swift 5.5. I have my async function, but whenever I try to call it, I get this error:
'async' call in a function that does not support concurrency
Here's the code sample:
class TryThis {
func getSomethingLater(_ number: Double) async -> String {
// test - sleep for 3 seconds, then return
Thread.sleep(forTimeInterval: 3)
return String(format: ">>>%8.2f<<<", number)
}
}
let tryThis = TryThis()
let result = await tryThis.getSomethingLater(3.141592653589793238462)
print("result: \(result)")
What's the solution for this??
The answer here is that the "await getSomethingLater" must be called from an async context. Literally that means changing this:
let result = await tryThis.getSomethingLater(3.141592653589793238462)
print("result: \(result)")
into this:
Task {
let result = await tryThis.getSomethingLater(3.141592653589793238462)
print("result: \(result)")
}
So the whole thing becomes:
class TryThis {
func getSomethingLater(_ number: Double) async -> String {
// test - sleep for 3 seconds, then return
Thread.sleep(forTimeInterval: 3)
return String(format: ">>>%8.2f<<<", number)
}
}
let tryThis = TryThis()
Task {
let result = await tryThis.getSomethingLater(3.141592653589793238462)
print("result: \(result)")
}
Here's the output:
result: >>> 3.14<<<
There's great info in the Meet async/await in Swift video from WWDC21.
When calling async code (including actor fields) from non-async code, you have to wrap it in a Task:
Task {
let result = await tryThis.getSomethingLater(3.141592653589793238462)
print("result: \(result)")
}
If you need it to escape that async context, you can use traditional callbacks:
func awaitedResult(callback: (String) -> Void) {
Task {
let result = await tryThis.getSomethingLater(3.141592653589793238462)
callback(result)
}
}
awaitedResult { result in
print("result: \(result)")
}
Swift by Sundell also offers some alternatives involving Combine
Further reading: Concurrency — The Swift Programming Language (Swift 5.5)
The original error is produced because "top-level" await is not yet supported in Swift (that is, code at the global scope).
The error just means you need to provide the async function with an asynchronous context, like using Task, Task.detached or a TaskGroup, depending on the behaviour you want.
Task.detached {
let result = await tryThis.getSomethingLater(3.141592653589793238462)
print("result: \(result)")
}
However, your code snippet should work in the future when top-level await is eventually proposed, implemented and supported.
Top-level await support was mentioned as part of the original proposal for async/await SE-0296.
In my case I just refactored the:
async { some-code-which-can-take-some-time }
with:
Task { some-code-which-can-take-some-time }
This error occurs when you’ve tried to call an async function from a synchronous function, which is not allowed in Swift – asynchronous functions must be able to suspend themselves and their callers, and synchronous functions simply don’t know how to do that.
func doAsyncWork() async {
print("Doing async work")
}
func doRegularWork() {
Task {
await doAsyncWork()
}
}
doRegularWork()
Reference here