My app downloads a couple of files from a server, using a URLSessionDataTask. When a downloads finishes successfully (and without any errors), then it should start the next download. If there is any type of error, then the whole thing has to abort and display the error message through the calling function. If it finishes without any errors, then it simply switches back to the calling function.
This function is called after another dataTask has finished (using a completion handler) but I never switch back to the main thread, so all of this is still running in the same background thread the previous task used.
My code (Swift 5, Xcode 14.2):
private func fileDownload(fileNames fns:[String]) {
if !errorBool {
print("Main thread: \(Thread.isMainThread)")
let group = DispatchGroup()
myloop:
for f in fns {
let url = getURL(f)
group.enter()
//DispatchQueue.global(qos: .background).async {
let task = session.dataTask(with: url) {(data, response, error) in
defer { group.leave() }
print("Starting task!")
if error != nil && data == nil {
self.errorBool = true
break myloop //TODO 1: "Cannot find label 'myloop' in scope", "self." doesn't help
}
if let httpResponse = response as? HTTPURLResponse {
//Do stuff with downloaded data, more error handling that sets the error flag
}
}
task.resume()
//}
//TODO 2: How do I wait here for the task to finish?
//group.wait()
if errorBool {
break myloop
}
}
group.notify(queue: .main) {
print("Done!")
//Displays any errors in a popup (on the main thread) through the calling function
}
}
}
There are two things that I'm experiencing problems with:
- How do I break the loop from within the task if there's an error ("TODO 1")?
- How do I wait at "TODO 2" until the task finishes, so I can break the loop if there are any errors? If I use
group.wait()there, then the task never starts (deadlock?), even though it should automatically run on a background thread. I tried to switch to yet another background thread for the task (see inactive code above) but that didn't help either.
I misunderstood how
DispatchGroup()works: You can enter the group and leave again once a loop iteration is done butgroup.wait()waits until everything (docs: "all tasks in the group") is done, not just a single loop iteration, blocking the thread it's currently working on in the process.To actually wait for a single task to finish I had to switch to
session.data(instead of the "old"session.dataTask):This code downloads all files in the correct order and waits for each download to finish before the next one is started.
The prints will look like this:
"End of function!" is printed first because of the
TaskinfileDownload. The downloads are going to wait for each other but the task won't and code after it will most likely be called before the first download is even done.Without the task, the
asynckeyword would be required for the function (andawaitin the calling function), which would then trickle up the whole function tree! This way this doesn't happen but you have to be careful about what code you want to be called when. If you want to wait forfileDownloadtoo, either useasyncin combination withawaitor add a completion handler.