Swift, dispatch_group_wait not waiting
When using dispatch_group_async
to call methods that are, themselves, asynchronous, the group will finish as soon as all of the asynchronous tasks have started, but will not wait for them to finish. Instead, you can manually call dispatch_group_enter
before you make the asynchronous call, and then call dispatch_group_leave
when the asynchronous call finish. Then dispatch_group_wait
will now behave as expected.
To accomplish this, though, first change downloadImage
to include completion handler parameter:
private func downloadImage(serverFile: AdFileInfo, completionHandler: (NSError?)->()) {
let destinationPath = imageDirectoryURL.URLByAppendingPathComponent(serverFile.fileName)
Alamofire.download(.GET, serverFile.imageUrl) { temporaryURL, response in return destinationPath }
.response { _, _, _, error in
if let error = error {
print("Error downloading \(serverFile.fileName): \(error)")
} else {
print("Done downloading \(serverFile.fileName)")
}
completionHandler(error)
}
}
I've made that a completion handler that passes back the error code. Tweak that as you see fit, but hopefully it illustrates the idea.
But, having provided the completion handler, now, when you do the downloads, you can create a group, "enter" the group before you initiate each download, "leave" the group when the completion handler is called asynchronously.
But dispatch_group_wait
can deadlock if you're not careful, can block the UI if done from the main thread, etc. Better, you can use dispatch_group_notify
to achieve the desired behavior.
func downloadImages(_ imageFilesOnServer: [AdFileInfo], completionHandler: @escaping (Int) -> ()) {
let group = DispatchGroup()
var downloaded = 0
group.notify(queue: .main) {
completionHandler(downloaded)
}
for serverFile in imageFilesOnServer {
group.enter()
print("Start downloading \(serverFile.fileName)")
downloadImage(serverFile) { error in
defer { group.leave() }
if error == nil {
downloaded += 1
}
}
}
}
And you'd call it like so:
downloadImages(arrayOfAdFileInfo) { downloaded in
// initiate whatever you want when the downloads are done
print("All Done! \(downloaded) downloaded successfully.")
}
// but don't do anything contingent upon the downloading of the images here
For Swift 2 and Alamofire 3 answer, see previous revision of this answer.
Swift, dispatch_group_wait not waiting
When using dispatch_group_async
to call methods that are, themselves, asynchronous, the group will finish as soon as all of the asynchronous tasks have started, but will not wait for them to finish. Instead, you can manually call dispatch_group_enter
before you make the asynchronous call, and then call dispatch_group_leave
when the asynchronous call finish. Then dispatch_group_wait
will now behave as expected.
To accomplish this, though, first change downloadImage
to include completion handler parameter:
private func downloadImage(serverFile: AdFileInfo, completionHandler: (NSError?)->()) {
let destinationPath = imageDirectoryURL.URLByAppendingPathComponent(serverFile.fileName)
Alamofire.download(.GET, serverFile.imageUrl) { temporaryURL, response in return destinationPath }
.response { _, _, _, error in
if let error = error {
print("Error downloading \(serverFile.fileName): \(error)")
} else {
print("Done downloading \(serverFile.fileName)")
}
completionHandler(error)
}
}
I've made that a completion handler that passes back the error code. Tweak that as you see fit, but hopefully it illustrates the idea.
But, having provided the completion handler, now, when you do the downloads, you can create a group, "enter" the group before you initiate each download, "leave" the group when the completion handler is called asynchronously.
But dispatch_group_wait
can deadlock if you're not careful, can block the UI if done from the main thread, etc. Better, you can use dispatch_group_notify
to achieve the desired behavior.
func downloadImages(_ imageFilesOnServer: [AdFileInfo], completionHandler: @escaping (Int) -> ()) {
let group = DispatchGroup()
var downloaded = 0
group.notify(queue: .main) {
completionHandler(downloaded)
}
for serverFile in imageFilesOnServer {
group.enter()
print("Start downloading \(serverFile.fileName)")
downloadImage(serverFile) { error in
defer { group.leave() }
if error == nil {
downloaded += 1
}
}
}
}
And you'd call it like so:
downloadImages(arrayOfAdFileInfo) { downloaded in
// initiate whatever you want when the downloads are done
print("All Done! \(downloaded) downloaded successfully.")
}
// but don't do anything contingent upon the downloading of the images here
For Swift 2 and Alamofire 3 answer, see previous revision of this answer.
dispatch_group_wait not waiting
Never mind, I solved by ditching the Facebook SDK and using contentsOfURL function instead because it's synchronous.
Dispatch Group wait stuck forever
Assuming that getActionField
is being called on the main queue and the understanding that Alamofire calls its completion blocks on the main queue (bad design in my opinion), you are running into a deadlock since the call to wait
is now blocking the main queue and none of the calls to leave
can be made.
You must never use the same thread to call wait
and leave
.
The simplest solution is to replace the use of wait
with notify
.
group.notify(queue: DispatchQueue.main) {
completion(resreturn)
}
You should avoid the use of wait
in general. Especially if you are already using a completion handler and there is no need for the method to wait.
How to handle group wait result in Swift 3
This code snippet raises a variety of different questions:
I notice that the behavior differs a bit between the playground and when you run it in an app. I suspect it's some idiosyncrasy of
needsIndefiniteExecution
ofPlaygroundPage
and GCD. I'd suggest testing this in an app. With the caveat of the points I raise below, it works as expected when I ran this from an app.I notice that you've employed this pattern:
__dispatch_group_async(group, queue) {
...
}I would suggest:
queue.async(group: group) {
...
}You are doing
group.suspend()
. A couple of caveats:One suspends queues, not groups.
And if you ever call
suspend()
, make sure you have a corresponding call toresume()
somewhere.Also, remember that
suspend()
stops future blocks from starting, but it doesn't do anything with the blocks that may already be running. If you want to stop blocks that are already running, you may want to cancel them.Finally, note that you can only suspend queues and sources that you create. You can't (and shouldn't) suspend a global queue.
I also notice that you're using
wait
on the same queue that you dispatched thetest()
call. In this case, you're getting away with that because it is a concurrent queue, but this sort of pattern invites deadlocks. I'd suggest avoidingwait
altogether if you can, and certainly don't do it on the same queue that you called it from. Again, it's not a problem here, but it's a pattern that might get you in trouble in the future.Personally, I might be inclined to use
notify
rather thanwait
to trigger the block of code to run when the two dispatched blocks are done. This eliminates any deadlock risk. And if I wanted to have a block of code to run after a certain amount of time (i.e. a timeout process), I might use a timer to trigger some cleanup process in case those two blocks were still running (perhaps canceling them; see How to stop a DispatchWorkItem in GCD?).
Dispatch Group wait till Timeout
stackoverflow.com/a/61192412/1801544 Please set timeout interval, I believe this is what you want
Waiting until the task finishes
If you need to hide the asynchronous nature of myFunction
from the caller, use DispatchGroup
s to achieve this. Otherwise, use a completion block. Find samples for both below.
DispatchGroup Sample
You can either get notified when the group's enter()
and leave()
calls are balanced:
func myFunction() {
var a = 0
let group = DispatchGroup()
group.enter()
DispatchQueue.main.async {
a = 1
group.leave()
}
// does not wait. But the code in notify() is executed
// after enter() and leave() calls are balanced
group.notify(queue: .main) {
print(a)
}
}
or you can wait:
func myFunction() {
var a = 0
let group = DispatchGroup()
group.enter()
// avoid deadlocks by not using .main queue here
DispatchQueue.global(qos: .default).async {
a = 1
group.leave()
}
// wait ...
group.wait()
print(a) // you could also `return a` here
}
Note: group.wait()
blocks the current queue (probably the main queue in your case), so you have to dispatch.async
on another queue (like in the above sample code) to avoid a deadlock.
Completion Block Sample
func myFunction(completion: @escaping (Int)->()) {
var a = 0
DispatchQueue.main.async {
let b: Int = 1
a = b
completion(a) // call completion after you have the result
}
}
// on caller side:
myFunction { result in
print("result: \(result)")
}
Why my DispatchGroup's wait always timeout?
A couple of thoughts:
As rmaddy said, if you want to wait 10 seconds, the correct syntax would be:
let waitResult = dispatchGroup.wait(timeout: .now() + 10)
Your syntax is not going to wait at all, and it's going to look like it instantly timed out.
You shouldn't be waiting at all.
First, you should never block the main thread. At worst, you could have your app killed by the watchdog process if you do this at the wrong time. At best, it's a horrible UX (i.e. the app will appear to be completely frozen to the end user).
Second, depending upon how
POSTRequest
was implemented, you might introduce a new problem. Specifically, ifPOSTRequest
calls its completion handler on the main queue (e.g. the default behavior for network libraries like Alamofire), you could deadlock until withwait
timeout on the main thread expires. If you are going to wait (which you shouldn't do anyway), you want to make sure you never wait on the same queue that your completion handler will use.Note, if the completion handler isn't running on the same queue that you are waiting, there is no deadlock risk, but you still suffer from the problem I mentioned earlier, namely that you're blocking the main thread, which should always be avoided.
You should instead move all that code inside the closure. And if that closure is running on a background thread, make sure to DispatchQueue.main.async { ... }
to dispatch it back to the main queue.
So, the correct pattern is something like the following.
If you want your request to timeout after 10 seconds, build that into the original request. Note the
timeoutInterval
:class func POSTRequest(accessPoint: String, params: [String: String]?, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) {
let url = URL(string: baseURLString)!
.appendingPathComponent(accessPoint)
let request = URLRequest(url: url, timeoutInterval: 10)
URLSession.shared.dataTask(with: request, completionHandler: completionHandler)
}Clearly, you may be doing something else in
POSTRequest
, so don't get lost in the details there. The key point is to build thetimeoutInterval
into the request.Then, make sure your
UIAlertAction
starts the request and simply puts all of the processing after thePOSTRequest
inside itscompletionHandler
, thereby eliminating the need for any dispatch group waiting or anything like that:let goAheadAction = UIAlertAction(title: "Go Ahead", style: .default) { _ in
let email = self.emailAddressTextField.text!
let parameters = ["emailAddress": email]
Server.POSTRequest(accessPoint: "/UserServices/forgotPassword", params: parameters) { data, response, error in
DispatchQueue.main.async {
guard error == nil else {
let alert = UIAlertController(title: "Time Out", message: "Failed to request service, reason: \"Time Out\"", preferredStyle: .alert)
alert.addAction( UIAlertAction(title: "Got it", style: .cancel))
self.present(alert, animated: true)
return
}
let alert = UIAlertController(title: "Email Sent", message: "We've sent an password reset link to \"\(email)\"", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Got it", style: .cancel))
self.present(alert, animated: true)
}
}
}This way, no threads are blocked.
setting wait time on dispatch_group
I hope, this example is 'self explanatory'. see, how timeout is defined!
import Foundation
let group = dispatch_group_create()
let queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL)
dispatch_async(queue) { () -> Void in
dispatch_group_enter(group)
var i = 0
while i < 15 {
print(i)
i++
usleep(500000)
}
dispatch_group_leave(group)
}
//dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
let t = dispatch_time( DISPATCH_TIME_NOW, 5000000000)
dispatch_group_wait(group, t)
print("stop")
/* prints
0
1
2
3
4
5
6
7
8
9
stop
*/
Declaration dispatch_time_t dispatch_time( dispatch_time_t when,
int64_t delta); Parameters when The dispatch_time_t value to use as
the basis for a new value. Pass DISPATCH_TIME_NOW to create a new time
value relative to now. delta The number of nanoseconds to add to the
time in the when parameter. Return Value A new dispatch_time_t.Discussion The default clock is based on mach_absolute_time.
You can use simplified version with the same functionality
import Foundation
let group = dispatch_group_create()
let queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL)
dispatch_group_async(group, queue) { () -> Void in
var i = 0
while i < 15 {
print(i)
i++
usleep(500000) // 0.5 sec delay
}
}
// wait time a little bit more than 1 second
let t = dispatch_time( DISPATCH_TIME_NOW, 1100000000)
dispatch_group_wait(group, t)
print("stop")
/* prints
0
1
2
stop
*/
with
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
the same code prints all 15 numbers (0...14)
Related Topics
Cancel Button Is Not Shown in Uisearchbar
How to Hide "Back to Safari" from Status Bar in iOS9
Xcode /Podfile.Lock: No Such File
How to Convert Ciimage to Uiimage in Swift 3.0
Hide Status Bar and Increase the Height of Uinavigationbar
iOS Fix Search Bar on Top of the Uitableviewcontroller
Getting the Action of Uigesturerecognizer in iOS
Using Wcsession with More Than One Viewcontroller
Odd Property Declaration Syntax Containing Angular Brackets <>
Does the List in Swiftui Reuse Cells Similar to Uitableview
Safari Web View Opening When Logging to Fb Through iOS 9
How to Create Multi Line Uisegmentedcontrol
How to Determine the Correct Altitude for an Mkmapcamera Focusing on an Mkpolygon