Multiple detached tasks are not executed at the same time - swift

class ViewModel: ObservableObject { }
struct ContentView: View {
#StateObject private var model = ViewModel()
var body: some View {
Button("Authenticate", action: doWork)
}
func doWork() {
Task.detached {
for i in 1...10_000 {
print("In Task 1: \(i)")
}
}
Task.detached {
for i in 1...10_000 {
print("In Task 2: \(i)")
}
}
}
}
This is the code described in https://www.hackingwithswift.com/quick-start/concurrency/whats-the-difference-between-a-task-and-a-detached-task.
Since the Tasks in doWork are detached, I expect them to be executed at the same time.
The above article also says so.
However, when I run this, Task2 is executed after Task1.
Am I wrong?

By putting the items into detached tasks, you are letting the operating system decide when to schedule them. Apparently, for one of the times you looked, the system decided to schedule them one after the other. There's nothing to say that the next time you look they won't be scheduled in parallel, or so that the second one runs before the first one.
The point is that you don't have any control over it. And the OS may make a different decision tomorrow than it does today. You've ceded that control to the OS by putting the work in Tasks.

Multiple detached tasks do run concurrently. Consider this example, where I perform a computationally intensive operation 20 times, each in its own task:
import SwiftUI
import os.log
private let log = OSLog(subsystem: "Detached tasks", category: .pointsOfInterest)
struct ContentView: View {
var body: some View {
Button("Do work", action: doWork)
}
func doWork() {
os_signpost(.event, log: log, name: #function)
for i in 0 ..< 20 {
Task.detached {
let id = OSSignpostID(log: log)
os_signpost(.begin, log: log, name: #function, signpostID: id, "start %d", i)
let value = calculatePi(decimalPlaces: 9)
print(value)
os_signpost(.end, log: log, name: #function, signpostID: id, "done")
}
}
}
// deliberately inefficient calculation of pi using Leibniz series
func calculatePi(decimalPlaces: Int = 9) -> Double {
let threshold = pow(0.1, Double(decimalPlaces))
var isPositive = true
var denominator: Double = 1
var value: Double = 0
var increment: Double
repeat {
increment = 4 / denominator
if isPositive {
value += increment
} else {
value -= increment
}
isPositive.toggle()
denominator += 2
} while increment >= threshold
return value
}
}
We can profile that task, using the “Points of Interest” tool. On the simulator that yields:
Note, that is artificially constraining the cooperative thread pool to two tasks at a time.
On an iPhone 12 Pro Max:
That runs six at a time.
And on an Intel 2018 MacBook Pro:
So, bottom line, it does run concurrently, constrained based upon the nature of the hardware on which you run it (with the exception of the simulator, which artificially constrains it even further).
FWIW, the 10,000 print statements is not a representative example because:
Too many synchronizations: The print statements are synchronized across threads which can skew the results. To test concurrency, you ideally want as few synchronizations taking place as possible.
Not enough work on each thread: A for loop of only 10,000 iterations is not enough work to properly manifest concurrency. It is quite easy that the first task could finish before the second even starts. And even if it does start interleaving at some point, you might see a couple thousand on one task, then a couple thousand on the next, etc. Do not expect a rapid interleaving of the print statements.
For these reasons, I replaced that for loop with a calculation of pi to 9 decimal places. The idea is to have some calculation/process that is sufficiently intensive to warrant concurrency and manifest the behavior we are looking for.
Perhaps needless to say, if we experience serial behavior if we:
move this calculation to an actor; and
launch with Task { ... } rather than detached task.
(Note, the horizontal time scale as been compressed to fit the twenty tasks on screen at the same time.)

Related

Having an #EnvironmentObject in the view causes Tasks to be executed on the main thread

I ran into an issue where my async functions caused the UI to freeze, even tho I was calling them in a Task, and tried many other ways of doing it (GCD, Task.detached and the combination of the both). After testing it I figured out which part causes this behaviour, and I think it's a bug in Swift/SwiftUI.
Bug description
I wanted to calculate something in the background every x seconds, and then update the view, by updating a #Binding/#EnvironmentObject value. For this I used a timer, and listened to it's changes, by subscribing to it in a .onReceive modifier. The action of this modifer was just a Task with the async function in it (await foo()). This works like expected, so even if the foo function pauses for seconds, the UI won't freeze BUT if I add one #EnvironmentObject to the view the UI will be unresponsive for the duration of the foo function.
GIF of the behaviour with no EnvironmentVariable in the view:
GIF of the behaviour with EnvironmentVariable in the view:
Minimal, Reproducible example
This is just a button and a scroll view to see the animations. When you press the button with the EnvironmentObject present in the code the UI freezes and stops responding to the gestures, but just by removing that one line the UI works like it should, remaining responsive and changing properties.
import SwiftUI
class Config : ObservableObject{
#Published var color : Color = .blue
}
struct ContentView: View {
//Just by removing this, the UI freeze stops
#EnvironmentObject var config : Config
#State var c1 : Color = .blue
var body: some View {
ScrollView(showsIndicators: false) {
VStack {
HStack {
Button {
Task {
c1 = .red
await asyncWait()
c1 = .green
}
} label: {
Text("Task, async")
}
.foregroundColor(c1)
}
ForEach(0..<20) {x in
HStack {
Text("Placeholder \(x)")
Spacer()
}
.padding()
.border(.blue)
}
}
.padding()
}
}
func asyncWait() async{
let continueTime: Date = Calendar.current.date(byAdding: .second, value: 2, to: Date())!
while (Date() < continueTime) {}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Disclaimer
I am fairly new to using concurrency to the level I need for this project, so I might be missing something, but I couldn't find anything related to the searchwords "Task" and "EnvironmentObject".
Question
Is this really a bug? Or am I missing something?
As far as I can tell, your code, with or without the #EnvrionmentObject, should always block the main thread. The fact that it doesn't without #EnvironmentObject may be a bug, but not the other way around.
In your example, you block the main thread -- you call out to an async function that runs on the context inherited from its parent. Its parent is a View, and runs on the main actor.
Usually in this situation, there's confusion about what actually runs something outside of the inherited context. You mentioned using Task.detached, but as long as your function was still marked async on the parent, with no other modifications, in would still end up running on the main actor.
To avoid inheriting the context of the parent, you could, for example, mark it as nonisolated:
nonisolated func asyncWait() async {
let continueTime: Date = Calendar.current.date(byAdding: .second, value: 2, to: Date())!
while (Date() < continueTime) {}
}
Or, you could move the function somewhere (like to an ObservableObject outside of the View) that does not explicitly run on the main actor like the View does.
Note that there's also a little bit of deception here because you've marked the function as async, but it doesn't actually do any async work -- it just blocks the context that it's running on.
The issue is that Task { ... } adds the task to the current actor. If you have some slow, synchronous task, you never want that on the main actor. People often conflate Task { ... } with DispatchQueue.global().async { ... }, but they are not the same thing.
And you should also avoid putting anything slow and synchronous in an #MainActor isolated function.
If you want to get some slow and synchronous process off the current actor, you would generally use Task.detached { ... }. Or you could create a separate actor for the time consuming process.
But in this case, there is no need to do any of this. Instead, use Task.sleep, which is a rendition of sleep designed for Swift concurrency which “doesn’t block the underlying thread.”
Button {
Task {
c1 = .red
try await Task.sleep(nanoseconds: 2 * NSEC_PER_SEC)
c1 = .green
}
} label: {
Text("Task, async")
}
.foregroundColor(c1)
Avoid spinning. Thread.sleep is a little better, but is still inadvisable. Use Task.sleep.

Swift async let cancellation doesn't work

I am a bit confused about tasks being cancelled.
Overview:
checkCancellation function has 2 child tasks, one task runs computeA and the other computeB. They run concurrently, computeB throws an error.
Doubt:
I expected child task computeA to be cancelled because computeB threw an error, but computeA was never cancelled.
Is my understanding wrong or am I missing something?
Or is this a bug?
Note:
I am using a SwiftUI project (as Swift Playgrounds don't support async let)
macOS Big Sur 11.5.2 (20G95)
Xcode Version 13.0 beta 5 (13A5212g)
Output:
A - started
B - going to throw
A - going to return, Task.isCancelled = false
error: infinity
Concurrent Function Definitions:
import Foundation
import UIKit
enum ComputationError: Error {
case infinity
}
fileprivate func computeA() async throws -> Int {
print("A - started")
await Task.sleep(2 * 100_000_000)
print("A - going to return, Task.isCancelled = \(Task.isCancelled)") //I expected Task.isCancelled to be true
return 25
}
fileprivate func computeB() async throws -> Int {
print("B - going to throw")
throw ComputationError.infinity
}
func checkCancellation() async throws {
async let a = computeA()
async let b = computeB()
let c = try await a + b
print("c = \(c)")
}
Invoking Concurrent function
struct ContentView: View {
var body: some View {
Button("check cancellation") {
Task {
do {
try await checkCancellation()
print("normal exit")
} catch {
print("error: \(error)")
}
}
}
}
}
Observation:
When I change the code to let c = try await b + a
Output:
A - started
B - going to throw
A - going to return, Task.isCancelled = true
error: infinity
Doubt:
I am still not sure I understand the reason for this behaviour in the original code
I expected child task computeA to be cancelled because computeB threw an error, but computeA was never cancelled. Is my understanding wrong or am I missing something?
Yes and yes. Your understanding is wrong and you are missing something.
The remark in the video has to do with subtasks created in a task group. What actually happens is that when the error percolates from the child task up to the task group, the task group calls cancel on all the other child tasks.
But you are testing using async let. There is no task group. So there is no one "there" to cancel the other subtask. In fact, if you watch the video carefully, you'll find that it tells you correctly what to expect:
If the parent task is cancelled, the child tasks are cancelled automatically.
If an async let task throws before a second async let task is even initialized, then the second task is "born cancelled". It is still awaited (the video is very clear about this, and it is correct); if it doesn't respond to cancellation by aborting, it will continue to completion.
The point here is that the parent task must end in good order. It cannot end until its children have completed one way or another. (Again, the video is very clear about this.) If that means automatically waiting for the tasks to complete in spite of the throw, then that's what will happen.
Another interesting thing to note is that with async let, where you say try await later, if the subtask throws, you won't even hear about it (and nothing at all will happen in this regard) until you reach the point where the try await takes place. In other words, the throw can only percolate up to the point where you actually say try; nothing else would make sense.

`sleep(_:)` vs `asyncAfter(deadline:execute:)`

I have a value type for which a relatively long calculation is require to produce it (> 1s). I wrap this value type in an enumeration that expresses whether it is currently being calculated or is available:
enum Calculatable<T> {
case calculated(T), calculating
}
The problem is this value is persisted. This means after the long running calculation, when I have the updated value, I try to persist it first before changing the property visible to the business logic. If persistence succeeds, the property is updated, if it fails, I want to wait and then try to persist again; with a catch — if any other values have changed that the calculated one depends on, we throw away the previously calculated value — stop trying to persist it — and submit a new Calculation operation to the queue — starting all over again. My initial attempt at that looked something like this:
/// Wraps a value that takes a long time to be calculated.
public class CalculatedValueWrapper<T> {
/// The last submitted `Calculation` to the queue.
private weak var latestCalculation: Optional<Operation>
/// The long running calculation that produces the up to date, wrapped value.
private let longCalculation: (_ cancelIf: () -> Bool) -> T
/// Calculation queue.
private let queue: OperationQueue
/// The calculated value.
private var wrappedValue: Calculatable<T>
/// Update the wrapped value.
public func update() {
// Don't set if already being calculated.
if case .calculated(_)=self.wrappedValue { self.wrappedValue = .calculating }
// Initiate new calculation.
self.latestCalculation?.cancel()
self.latestCalculation={
let calc: Calculation = .init(self, self.longCalculation)
self.queue.addOperation(calc)
return calc
}()
}
}
/// Executes a long running calculation and persists the result once complete.
class Calculation<T>: Operation {
/// The calculation.
private let calculation: (_ cancelIf: () -> Bool) -> T
/// The owner of the wrapped value we're calculating.
private weak var owner: Optional<CalculatedValueWrapper<T>>
func main() {
let result=self.calculation(cancelIf: { [unowned self] in self.isCancelled })
let persist: () -> Bool={
// In case the persistence fails.
var didPersist=false
// Persist the result on the main thread.
DispatchQueue.main.sync { [unowned self] in
// The owner may have been deallocated between the time this dispatch item was submitted and the time it began executing.
guard let owner=self.owner else { self.cancel(); return }
// May have been cancelled.
guard !self.isCancelled else { return }
// Attempt to persist the calculated result.
if let _=try? owner.persist(result) {
didPersist=true
}
}
// Done.
return didPersist
}
// Persist the new result. If it fails, and we're not cancelled, keep trying until it succeeds.
while !self.isCancelled && !self.persist() {
usleep(500_000)
}
}
}
I would have stuck with this, but after further research I noticed a prevailing sentiment that it was bad practice to sleep the thread in DispatchQueue items and Operations. The alternative that seems to be considered better, in the case of DispatchQueue for example, was to initiate another DispatchQueue item using asyncAfter(deadline:execute:).
That solution appears more complicated in my case as being able to cancel a single operation that encapsulates everything that needs to be done makes cancelling easy. I can hold a reference to the last Calculation operation executed, and if another one is submitted on the main thread while one is in progress, I cancel the old one, add the new one, and with that I know the old value won't be persisted because it is done on the main thread after cancellation has already been performed; and a Calculation must not be cancelled in order for it to persist its result.
Is there something about DispatchQueues or OperationQueues that would make the alternative solution more straightforward to implement in this case? Or is sleeping here totally fine?
Make a RetryPersist NSOperation. Just before adding it to the queue, set its isReady to false. Have it do a dispatchAfter weakly capturing itself to set its isReady to true. Now you can cancel any RetryPersist operations in the queue, if the count of them where not ready, not cancelled is not zero you know there’s one waiting to go etc.

DispatchWorkItem How to cancel recursive process?

I have a class Complicated, where (it's not a real code):
class BeReadyInSomeTime {
var someData: SomeData
var whenDone: () -> Void
var isDone: Bool = false
var highRes: [LongCountedStuff] = []
init(data:SomeData, whenDone: #escaping () - >Void) {
self.someData = someData
self.whenDone = whenDone
... prepare `highRes` in background...
{ makeHighRes() }
... and when done set `isDone` to `true`, fire `whenDone()`
}
func reset(data:SomeData) {
self.someData = someData
self.isDone = false
self.highRes = []
... forget **immediately** about job from init or reset, start again
{ makeHighRes() }
... and when done set `isDone` to `true`, fire `whenDone()`
}
var highResolution:AnotherType {
if isDone {
return AnotherType(from: highRes)
} else {
return AnotherType(from: someData)
}
}
func makeHighRes() {
var result = [LongCountedStuff]
// prepare data, fast
let some intermediateResult = almost ()
self.highRes = result
}
func almost() -> [LongCountedStuff] {
if isNice {
return countStuff(self.someData)
} else {
return []
}
func countStuff(stuff:[LongCountedStuff], deep:Int = 0) -> [LongCountedSuff] {
if deep == deep enough {
return stuff
} else {
let newStuff = stuff.work
count(newStuff, deep: deep+1)
}
}
Making highRes array is a recurrent function which calls itself many times and sometimes it takes seconds, but I need feedback as fast as possible (and it will be one of someData elements, so I'm safe). As far I know, I can only 'flag' DispatchWorkItem that's cancelled. If I deliver new data by reset few times per second (form mouse drag) whole block is counted in background as many times as data was delivered. How to deal with this kind of problem? To really break counting highRes?
If you have a routine that is constantly calling another framework and you want to stop it at the end of one iteration and before it starts the next iteration, then wrapping this in an Operation and checking isCancelled is a good pattern. (You can also use GCD and DispatchWorkItem and use its isCancelled, too, but I find operations do this more elegantly.)
But if you’re saying you not only want to cancel your loop, but also hope to stop the consuming call within that framework, then, no, you can’t do that (unless the framework provides some cancelation mechanism of its own). But there is no preemptive cancellation. You can’t just stop a time consuming calculation unless you add checks inside that calculation to check to see if it has been canceled.
I’d also ask whether the recursive pattern is right here. Do you really need the results of one calculation in order to start the next? If so, then a recursive (or iterative) pattern is fine. But if the recursive operation is just to pass the next unit of work, then a non-recursive pattern might be better, because it opens up the possibility of doing calculations in parallel.
For example, you might create a concurrent queue with a maxConcurrencyCount of some reasonable value (e.g. 4 or 6). Then wrap each individual processing task in its own Operation subclass and have each check its respective isCancelled. Then you can just add all the operations up front, and let the queue handle it from there. And when you want to stop them, you can tell the queue to cancelAllOperations. It’s a relative simple pattern, allows you to do calculations in parallel, and is cancelable. But this obviously only works if a given operations is not strictly dependent upon the results of the prior operation(s).

Delay in unit test

So I have a unit test to test if clinics are being updated every 10 seconds. After 5 seconds, I clear all of the clinics. Then set an expectation that times out after 9 seconds to make sure clinics were updated. Here is my code:
func testRefresh() {
let expec = expectation(description: "Clinics timer expectation")
let expec2 = expectation(description: "Clinics timer expectation2")
expec2.isInverted = true
let dispatchGroup = DispatchGroup(count: 5)
dataStore.load()
wait(for: [expec2], timeout: 5.0) // This is what I am asking about
self.dataStore.clinicsSignal.fire([])
dataStore.clinicsSignal.subscribeOnce(with: dispatchGroup) {
print("clinics signal = \($0)")
expec.fulfill()
}
wait(for: [expec], timeout: 9.0)
XCTAssertFalse(self.dataStore.clinics.isEmpty)
}
I want to have that delay for 5 seconds. Using an inverted expectation the way I did is the only way I could find to make it work. I just think using an inverted expectation is bad practice.
If I use sleep(5) it stops the whole program for 5 seconds. I have also tried a solution using DispatchQueue.main.asyncAfter like outlined here but to no avail.
I have two suggestions to use together:
Use a spy test double to make sure that the service your data store uses to refresh the clinics is called twice
Inject the refresh interval to make the tests faster
Spy test double
Testing the side effect of the data loading, that it hits the service, could be a way to simplify your test.
Instead of using different expectations and exercising the system under test in a way that might not be what happens at runtime (the dataStore.clinicsSignal.fire([])) you can just count how many times the service is hit, and assert the value is 2.
Inject refresh interval
The approach I would recommend is to inject the time setting for how frequently the clinics should be updated in the class, and then set a low value in the tests.
After all, I'm guessing what you are interested in is that the update code runs as expected, not every 10 seconds. That is, it should update at the frequency you set.
You could do this by having the value as a default in the init of your data store, and then override it in the tests.
The reason I'm suggesting to use a shorter refresh interval is that in the context of unit testing, the faster they run the better it is. You want the feedback loop to be as fast as possible.
Putting it all together, something more or less like this
protocol ClinicsService {
func loadClinics() -> SignalProducer<[Clinics], ClinicsError>
}
class DataSource {
init(clinicsService: ClinicsService, refreshInterval: TimeInterval = 5) { ... }
}
// in the tests
class ClinicsServiceSpy: ClinicsService {
private(var) callsCount: Int = 0
func loadClinics() -> SignalProducer<[Clinics], ClinicsError> {
callsCount += 1
// return some fake data
}
}
func testRefresh() {
let clinicsServiceSpy = ClinicsServiceSpy()
let dataStore = DataStore(clinicsService: clinicsServiceSpy, refreshInterval: 0.05)
// This is an async expectation to make sure the call count is the one you expect
_ = expectation(
for: NSPredicate(
block: { input, _ -> Bool in
guard let spy = input as? ClinicsServiceSpy else { return false }
return spy.callsCount == 2
),
evaluatedWith: clinicsServiceSpy,
handler: .none
)
dataStore.loadData()
waitForExpectations(timeout: .2, handler: nil)
}
If you also used Nimble to have a more refined expectation API your test could look like this:
func testRefresh() {
let clinicsServiceSpy = ClinicsServiceSpy()
let dataStore = DataStore(clinicsService: clinicsServiceSpy, refreshInterval: 0.05)
dataStore.loadData()
expect(clinicsServiceSpy.callsCount).toEventually(equal(2))
}
The tradeoff you make in this approach is to make the test more straightforward by writing a bit more code. Whether it's a good tradeoff is up to you do decide.
I like working in this way because it keeps each component in my system free from implicit dependencies and the test I end up writing are easy to read and work as a living documentation for the software.
Let me know what you think.