I'm using a very simple swift project created with SPM where it includes Alamofire.
main.swift:
import Alamofire
Alamofire.request("https://google.com").responseString(queue: queue) { response in
print("\(response.result.isSuccess)")
}
The closure is never executed if I don't use a lock.
Is there a way to instruct to wait for all threads or that specific thread before exiting?
I'm aware this can be easily achieved using Playgrounds.
Simplest way to wait for an async task is to use a semaphore:
let semaphore = DispatchSemaphore(value: 0)
doSomethingAsync {
semaphore.signal()
}
semaphore.wait()
// your code will not get here until the async task completes
Alternatively, if you're waiting for multiple tasks, you can use a dispatch group:
let group = DispatchGroup()
group.enter()
doAsyncTask1 {
group.leave()
}
group.enter()
doAsyncTask2 {
group.leave()
}
group.wait()
// You won't get here until all your tasks are done
For Swift 3
let group = DispatchGroup()
group.enter()
DispatchQueue.global(qos: .userInitiated).async {
// Do work asyncly and call group.leave() after you are done
group.leave()
}
group.notify(queue: .main, execute: {
// This will be called when block ends
})
This code will be helpful when you need to execute some code after some task is done.
Please add details about your question, then I can help you more.
Related
In a swift 2 command line tool (main.swift), I have the following:
import Foundation
print("yay")
var request = HTTPTask()
request.GET("http://www.stackoverflow.com", parameters: nil, completionHandler: {(response: HTTPResponse) in
if let err = response.error {
print("error: \(err.localizedDescription)")
return //also notify app of failure as needed
}
if let data = response.responseObject as? NSData {
let str = NSString(data: data, encoding: NSUTF8StringEncoding)
print("response: \(str)") //prints the HTML of the page
}
})
The console shows 'yay' and then exits (Program ended with exit code: 0), seemingly without ever waiting for the request to complete. How would I prevent this from happening?
The code is using swiftHTTP
I think I might need an NSRunLoop but there is no swift example
Adding RunLoop.main.run() to the end of the file is one option. More info on another approach using a semaphore here
I realize this is an old question, but here is the solution I ended on. Using DispatchGroup.
let dispatchGroup = DispatchGroup()
for someItem in items {
dispatchGroup.enter()
doSomeAsyncWork(item: someItem) {
dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: DispatchQueue.main) {
exit(EXIT_SUCCESS)
}
dispatchMain()
You can call dispatchMain() at the end of main. That runs the GCD main queue dispatcher and never returns so it will prevent the main thread from exiting. Then you just need to explicitly call exit() to exit the application when you are ready (otherwise the command line app will hang).
import Foundation
let url = URL(string:"http://www.stackoverflow.com")!
let dataTask = URLSession.shared.dataTask(with:url) { (data, response, error) in
// handle the network response
print("data=\(data)")
print("response=\(response)")
print("error=\(error)")
// explicitly exit the program after response is handled
exit(EXIT_SUCCESS)
}
dataTask.resume()
// Run GCD main dispatcher, this function never returns, call exit() elsewhere to quit the program or it will hang
dispatchMain()
Don't depend on timing.. You should try this
let sema = DispatchSemaphore(value: 0)
let url = URL(string: "https://upload.wikimedia.org/wikipedia/commons/4/4d/Cat_November_2010-1a.jpg")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
print("after image is downloaded")
// signals the process to continue
sema.signal()
}
task.resume()
// sets the process to wait
sema.wait()
If your need isn't something that requires "production level" code but some quick experiment or a tryout of a piece of code, you can do it like this :
SWIFT 3
//put at the end of your main file
RunLoop.main.run(until: Date(timeIntervalSinceNow: 15)) //will run your app for 15 seconds only
More info : https://stackoverflow.com/a/40870157/469614
Please note that you shouldn't rely on fixed execution time in your architecture.
Swift 4: RunLoop.main.run()
At the end of your file
// Step 1: Add isDone global flag
var isDone = false
// Step 2: Set isDone to true in callback
request.GET(...) {
...
isDone = true
}
// Step 3: Add waiting block at the end of code
while(!isDone) {
// run your code for 0.1 second
RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.1))
}
We can use a DispatchGroup in swift to add many tasks to the group. The group will wait until all the tasks are complete before proceeding to the next codes.
let dg = DispatchGroup()
dg.notify(queue: .global()) {
// run code here on completion
}
Is there a way to add a timeout for this dispatchGroup in case the tasks are taking too long to complete?
[Edit]
I am aware that DispatchGroup.wait(timeout:) adds a timeout. But this makes it synchronously wait. Is there a way that it is asynchronous using the notify method, but still have a timeout?
If you want the DispatchGroup to have a timeout asynchronously try:
var dg: DispatchGroup? = DispatchGroup()
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
self.dg = nil // after time out group is removed asynchronously
}
dg?.notify(queue: .global()) {
// run code here on completion
}
Just to add an alternative way; to easily reuse this in multiple parts of your code, you could provide a DispatchGroup extension like this:
extension DispatchGroup {
func notify(queue: DispatchQueue, timeout: TimeInterval, execute work: #escaping () -> ()) {
var selfDestructingCompletion: (() -> ())?
selfDestructingCompletion = {
selfDestructingCompletion = nil
work()
}
self.notify(queue: queue) {
selfDestructingCompletion?()
}
queue.asyncAfter(deadline: .now() + timeout) {
selfDestructingCompletion?()
}
}
}
Which then you would just use like this:
// 1. something you need to have for the dispatch group anyway
let someSerialQueue = DispatchQueue(label: "someQueue")
let group = DispatchGroup()
for someApi in apiList {
group.enter()
someApi.call() {
group.leave()
}
}
// 2. the new method, just with the timeout parameter
group.notify(queue: someSerialQueue, timeout: 5.0) {
// Run the completion for completing the tasks or for timeout in one place
}
This has the added benefits of:
having no thread issues as long as you pass a serial queue (not true for the accepted answer)
calling the completion only in one place when it's used
Just be sure to prefix the name of the method to some specific namespace of yours if you plan to insert this in a public library, to avoid clashes with other similar implementations of this extension.
Basically, at the launch of my app, I want to load the latest data from firebase from about 4-5 different documents. Then I also want to set up a listener to monitor data changes. I do this by calling 4-5 similar functions that take a dispatchGroup as an argument. I may be approaching this completely wrong but I could not think of any other way to do it. I just want to load those documents, set up listeners, and take certain action whenever those docs are loaded at the launch of the app.
// app launch
let dispatch = DispatchGroup()
getFirebaseDocument1(dispatch: dispatch)
getFirebaseDocument2(dispatch: dispatch)
getFirebaseDocument3(dispatch: dispatch)
getFirebaseDocument4(dispatch: dispatch)
getFirebaseDocument5(dispatch: dispatch)
dispatch.notify(queue:main) {
// execute some code to execute after all the documents are fetched
}
// typical getFirebaseDocument code
dispatch.enter()
let ref = someFirestoreReference
ref.addSnapshotListener { (snapshot, error) in
if let error = error{
// handle error
} else {
// load the document
}
dispatch.leave()
}
The code works fine when it's launched but crashes whenever the listener receives an update. I know this is because the dispatch.leave() is called in the listener function. However, I cannot seem to figure out a clever solution to where I can asynchronously load the data from firebase at launch while also setting up listeners. I would also prefer not to nest closures within one another as it wouldn't be asynchronous and it would also be a pain.
I may be wrong, but you should leave the group inside your block, here is the example how I do it using group
class func getRates(completion: #escaping EmptyBlock) {
let eurRequest = APIConfigs.request(part: "rs/price/history")
let usdRequest = APIConfigs.request(part: "rs/price/history/usd")
let group = DispatchGroup()
group.enter()
sendRequest(request: eurRequest, method: .get, parameters: ServerParameters.rates()) { response in
Course.current.addRate(rates: ratesRequest(response: response), type: .eur)
group.leave()
}
group.enter()
sendRequest(request: usdRequest, method: .get, parameters: ServerParameters.rates()) { response in
Course.current.addRate(rates: ratesRequest(response: response), type: .usd)
group.leave()
}
group.notify(queue: .main) {
completion()
}
}
What i am doing wrong? At playground it runs as it should. But as soon as i deploy it on iOS simulator it returns the wrong sequence.
#objc func buttonTapped(){
let group = DispatchGroup()
let dispatchQueue = DispatchQueue.global(qos: .default)
for i in 1...4 {
group.enter()
dispatchQueue.async {
print("๐น \(i)")
}
group.leave()
}
for i in 1...4 {
group.enter()
dispatchQueue.async {
print("โ \(i)")
}
group.leave()
}
group.notify(queue: DispatchQueue.main) {
print("jobs done by group")
}
}
Console Output:
I don't get it. ๐
You should put the group.leave() statement in the dispatchQueue.async block as well, otherwise it will be executed synchronously before the async block would finish execution.
#objc func buttonTapped(){
let group = DispatchGroup()
let dispatchQueue = DispatchQueue.global(qos: .default)
for i in 1...4 {
group.enter()
dispatchQueue.async {
print("๐น \(i)")
group.leave()
}
}
for i in 1...4 {
group.enter()
dispatchQueue.async {
print("โ \(i)")
group.leave()
}
}
group.notify(queue: DispatchQueue.main) {
print("jobs done by group")
}
}
As Dรกvid said, properly employed dispatch groups only ensure that the notification takes place after all of the tasks finish, which you can achieve by calling leave from within the dispatched blocks, as he showed you. Or alternatively, since your dispatched tasks are, themselves, synchronous, you don't have to manually enter and leave the group, but can use group parameter of async method:
let group = DispatchGroup()
let queue = DispatchQueue.global(qos: .default)
for i in 1...4 {
queue.async(group: group) {
print("๐น \(i)")
}
}
for i in 1...4 {
queue.async(group: group) {
print("โ \(i)")
}
}
group.notify(queue: .main) {
print("jobs done by group")
}
Use group.enter() and group.leave() when calling some asynchronous method, but in the case of these print statements, you can just use async(group:execute:) as shown above.
Now, we've solved the problem where the "jobs done by group" block didn't wait for all of the dispatched tasks. But, because you're doing all of this dispatching to a concurrent queue (all the global queues are concurrent queues), you have no assurances that your tasks will be performed in the order that you requested. They're queued up in a strict FIFO manner, but because they're concurrent, you have no assurances when you'll hit the respective print statements.
If you need it to print the messages in order, you will have to use a serial queue. For example, if you create your own queue, in the absence of the .concurrent attribute, the following will create a serial queue:
// create serial queue
let queue = DispatchQueue(label: "...")
// but not your own concurrent queue:
//
// let queue = DispatchQueue(label: "...", attributes: .concurrent)
//
// nor one of the global concurrent queues:
//
// let queue = DispatchQueue.global(qos: .default)
//
And if you run the above code with this serial queue, you'll see what you were looking for:
๐น 1
๐น 2
๐น 3
๐น 4
โ 1
โ 2
โ 3
โ 4
jobs done by group
But, then, again, if you were using a serial queue, the group would be completely unnecessary (you could just add the "completion" task as yet another dispatched task at the end of the serial queue). I only show the use of serial queues as a way to avoid the race condition of dispatching eight tasks to a concurrent queue.
I'm trying to use Dispatch group to notify me when a task is finished running. I've written a simple pseudo of what I'm trying to accomplish. For some reason my notify function gets called first.
class Main {
let cats = Cats()
let all = ALL.singleton
viewdidLoad(){
cats.makeAllCall
all.dis()
}
}
class Cats {
let dispatch = DispatchGroup()
let all = ALL.singleton
func makeAllCall(){
for i in 1...10{
all.callInfo()
print("hello")
}
}
}
class ALL {
static let singleton = ALL()
let dispatch = DispatchGroup()
func dis(){
dispatch.notify(.main){
print("working")
}
}
func callInfo(){
dispatch.enter()
Alamofire.request("url", headers: headers).responseJSON { response in
if response.result.isSuccess{
completion(JSON(response.result.value!))
}else{
print("Binance - Couldn't import Request: Please check your internet connection")
}
}
dispatch.leave()
}
}
You have not understood how dispatch groups work. You are calling dis(), apparently in the belief that dispatch.notify is something that you call. It isn't. It is called for you when every enter has been balanced by a leave. A typical structure looks like this pseudo-code:
let group = DispatchGroup()
// here we go...
group.enter()
_queue1_.async {
// ... do task here ...
group.leave()
}
group.enter()
_queue2_.async {
// ... do task here ...
group.leave()
}
group.enter()
_queue3_.async {
// ... do task here ...
group.leave()
}
// ... more as needed ...
group.notify(queue: DispatchQueue.main) {
// finished!
}
You need to get rid of this bizarre class structure and put everything โ the dispatch group, the enter and leave calls, and the notify block โ together in one place. If you don't want to do that, then this is not a good use of a dispatch group (perhaps what you wanted was something like Operation and OperationQueue).