I'm having trouble finding documentation that discusses if Tasks are run concurrently. Or if tasks are run in sequence, in some invisible queue.
The following is a stripped-down problem I'm having with my app, which can be run in a playground, that prompted this question.
import UIKit
import Foundation
import Combine
struct Info {
var id: String
var value: Int
}
class DataStore {
// pretend this is storing into core data
func store(info: Info, id: String) {
print(" store \(info)")
let start = CACurrentMediaTime()
while CACurrentMediaTime() - start < 2 { }
}
}
let dataStore = DataStore()
let subj = PassthroughSubject<Info, Never>()
let cancel = subj.sink { info in
print("Start task for \(info)")
// is there a way to queue tasks so that we
Task {
print(" start \(info)")
dataStore.store(info: info, id: info.id)
print(" finish: \(info)")
}
}
subj.send(Info(id: "A", value: 1))
subj.send(Info(id: "A", value: 2))
subj.send(Info(id: "A", value: 3))
subj.send(Info(id: "A", value: 4))
let queueA = DispatchQueue(label: "A", attributes: .concurrent)
let queueB = DispatchQueue(label: "B", attributes: .concurrent)
queueA.async {
subj.send(Info(id: "A", value: 1))
subj.send(Info(id: "A", value: 2))
subj.send(Info(id: "A", value: 3))
subj.send(Info(id: "A", value: 4))
}
queueB.async {
subj.send(Info(id: "B", value: 1))
subj.send(Info(id: "B", value: 2))
subj.send(Info(id: "B", value: 3))
subj.send(Info(id: "B", value: 4))
}
queueA.async {
subj.send(Info(id: "A", value: 1))
subj.send(Info(id: "A", value: 2))
subj.send(Info(id: "A", value: 3))
subj.send(Info(id: "A", value: 4))
}
queueB.async {
subj.send(Info(id: "B", value: 1))
subj.send(Info(id: "B", value: 2))
subj.send(Info(id: "B", value: 3))
subj.send(Info(id: "B", value: 4))
}
// Note that a closure is not started until the other one has finished
Notice how a closure is never started before the previous one has finished. Now I don't know if that's because the passthrough subject is keeping things in sequence or something else with publishers.
I understand it's not a perfect example because of the publisher but my app has old Combine code interfacing with newer async-await code.
P.S. Would it make a difference if I used async sequence instead of a publisher?
CodePudding user response:
You ask whether async-await tasks run in sequence or not. The answer is “it depends”.
You can see that the tasks are running in parallel.
Now this is bit of an exception to the rule. This is merely a demonstration that using Task
would not necessarily be sufficient, alone, to avoid parallel execution.
But we often use tasks in conjunction with an actor. In that case, you do achieve non-parallel execution .
So, if I make the Experiment
an actor, the methods will be actor-isolated, and therefore will not run in parallel. Now, one could make it run on the main actor with @MainActor
qualifier (which would be bad in this example, because it would block the main thread) or, as shown below, just make it a separate actor:
actor Experiment {
func spin(index: Int, for interval: TimeInterval) {
...
}
}
Yielding:
In your case, if your DataStore
was an actor, that would prevent concurrent execution. And, FWIW, store
should not be marked as an async
method, because it does nothing asynchronous (at least, at this point).
A few other caveats about your example:
Your task is so quick that it is hard to see if it is really running sequentially or in parallel: Note that I performed my tests with an artificially slower example, precisely to clearly manifest parallel vs non-concurrent execution.
You did your tests in a playground: IMHO, playgrounds are not a good environment for testing concurrency. You want to run this in a test app that runs in an environment that parallels that of your final target platform. Playgrounds feature all sorts of idiosyncratic behaviors from which it would be too easy to draw incorrect conclusions.
I would advise against mixing Combine and (especially) GCD with async-await code.
So, run it in a real app, and if you want to ensure it will not run in parallel, make sure your method is isolated to an actor.