I have just discovered Task.immediate.
This is a big discovery for me, because my UIKit apps use a "presenter / processor" architecture where all user inputs received by a view controller ("presenter") are immediately passed along to a business logic object ("processor") — and where, because of possible multithreading in some of the business logic, the function that does this passing-along is async, and so require an await. Thus my apps are full of code that with a structure like this:
/// The user tapped the Stats button.
@IBAction func doStats(_ sender: UIButton) {
Task {
await processor?.receive(.stats(source: sender))
}
}
The pattern here is that an @IBAction or other @objc func is called on the main thread by the runtime, and we immediately turn around and signal the processor that this has happened. But because we must say await to talk to the processor, there has to be a Task initializer (because every async calling chain must be "rooted" somewhere, and since there is no way to tell the runtime to call me async, an explicit Task initializer has to be the "root" of the chain).
In every case, the Task initializer is the last (and usually, the only) thing in the func.
Now then. It has always bothered me, on multiple grounds, that a Task initializer is merely a scheduler. For one thing, a note of indeterminacy is introduced; there is in fact a risk that the user could do thing in very quick succession and the corresponding Tasks could be launched in the wrong order. For another, there's a delay. I can sense the existence of this delay in the app's response to the user's actions, and I can (and do) prove its existence in my unit tests; if I say
@Test func stats() {
let button = UIButton()
subject.doStats(button)
#expect(processor.thingsReceived == .stats(source: button))
}
...the test fails, because I need to introduce a delay between the doStats call and the #expect that checks the outcome of that call — a delay that is due to the Task initializer.
Well, when I learned of Task.immediate, my little heart went pit-a-pat. Wouldn't this eliminate the delay? Sure enough, if I change Task to Task.immediate in my IBAction func, the very same test passes.
We come at last to my questions.
- Before I go changing all my code in all my apps, is this a good, legitimate, and valid use of
Task.immediate? - More controversially, perhaps: In situations like this (where "like this" means that the Task initializer is the last thing in the call chain (and perhaps also that the call chain is initiated by the runtime)), couldn't / shouldn't the compiler treat the Task initializer as meaning
Task.immediate, thus eliminating the delays automatically?