TL;DR see below for a code example of the problem.
I have two functions async update()
that updates some data on the server and waits for the response. I have the option to provide a callback or make the function async and wait for the response. The second function fetch()
lives in a different class (actually a different @StateObject, i.e. ObservableObject class - I'm using SwiftUI).
The function fetch()
is triggered from a view, but it should "wait" for the update()
function to have finished (if there is one running) before executing.
I want to keep the two class instances -where these functions are part of- separate. Thus I cannot explicitly use callback or alike. After searching and discovering DispatchQueue
, DispatchGroup
, Task
, OperationQueue
and concepts of synchronous/asynchronous, serial/concurrent I am quite confused on what to use since many of them seem to be very similar tools.
DispatchQueue (& Task)
At first I thought DispatchQueue was the way to go since the docs seem to describe exactly what I need: "... Dispatch queues execute tasks either serially or concurrently. ..." The DispatchQueue.main is serial so I thought simply executing both function calls like this would wait for the
update()
to finish if there was one started anywhere in my app andfetch()
would run afterwards. But apparently:DispatchQueue.main.async { self.example = await self.update() }
That throws an error:
Cannot pass function of type '() async -> ()' to parameter expecting synchronous function type
That already seems weird to me because of its name
DispatchQueue.main.
async
. I can avoid this by wrapping the call in aTask {}
but then I suppose it gets run on a different thread, as it did not finish theTask
before running thefetch()
function. Thus it was no longer serial:DispatchQueue.main.async { Task { print("1") try! await Task.sleep(nanoseconds: 10_000_000_000) print("2") } Task { print("3") } } // prints 1,3, ..., 2
Using
DispatchQueue.main.sync
could seem more of what I need according to this, but I get the error:No exact matches in call to instance method 'sync'
Is there a way to use
DispatchQueue
to accomplish my goal? (I also tried creating my own queue with the same result as using the global main queue. I would want to use an asynchronous function and block any other function on this queue until after finishing execution)DispatchGroup
Next I tried using
DispatchGroup
as shown here but was already thrown off by having to pass around thisDispatchGroup()
instance to reference in both classes. Is it the only way to accomplish my goal? Could I avoid passing one single object to both classes at initalisation?OperationQueue
After reading further I stumbled across
OperationQueue
here. Again, this seems to solve my issue, but I once again have to pass thisOperationQueue
object to both classes.
Can someone explain the distinguishing differences of these approaches in relation to my problem? Which one is the "proper" way to do it? I would assume not having to pass around some objects is easier, but then how can I use a global DispatchQueue
to serially execute some async functions?
Here a MRE, after clicking "start long task" and right away fetch the console should read: "1","2","these would be some results"
import SwiftUI
class Example1ManagerState: ObservableObject {
func update() async {
print("1")
try! await Task.sleep(nanoseconds: 10_000_000_000)
print("2")
}
}
class Example2ManagerState: ObservableObject {
func fetch() {
print("these would be some results")
}
}
struct ContentView2: View {
@StateObject var ex1 = Example1ManagerState()
@StateObject var ex2 = Example2ManagerState()
var body: some View {
Button {
Task {
await ex1.update()
}
} label: {
Text("start long task")
}
Button {
ex2.fetch()
} label: {
Text("fetch")
}
}
}