I am trying to understand better the use of dispatch_semaphore_wait. I get the idea of this use but i am not getting on Which thread i am actually wait. if i will do that on the Main thread i will block the main thread, or if i will do it like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef error) {
dispatch_semaphore_signal(sema);
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
});
On which thread i will wait ?
or if i would use DISPATCH_QUEUE_PRIORITY_DEFAULT instead or DISPATCH_QUEUE_PRIORITY_BACKGROUND ?
In 1 and 2 you'd wait on the thread where the DISPATCH_QUEUE_PRIORITY_HIGH and DISPATCH_QUEUE_PRIORITY_BACKGROUND resides. In this case its the same "queue"/thread and the difference relies in the way the scheduler handles the selection of the tasks submitted (high, mid or low priority. Where low is equal to DISPATCH_QUEUE_PRIORITY_BACKGROUND and high as DISPATCH_QUEUE_PRIORITY_HIGH). The do not rely on the UI-thread what so ever. The dispatch_get_global_queue call does point to a thread that iOS creates for you. There are lots of guides on the net describing this and how the scheduling of tasks are handled in a multithreaded environment.
Google is your friend ;)
If you are attempting to find out synchronously if the user has given address book access I would suggest the following:
__block BOOL accessGranted = NO;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
accessGranted = granted;
if(semaphore) {
dispatch_semaphore_signal(semaphore);
}
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_release(semaphore);
semaphore = NULL;
return accessGranted;
You can't be sure in which thread will you wait by calling dispatch_semaphore_wait() in the global queue, because:
global queue is not a single thread, it is a pool of threads.
only serial queue can be one-threaded, but that is not specified in Apple Documentation.
So, to be sure you are not blocking anything, better to create your own serial queue for waiting the semaphore.
Related
I am creating a serial queue in which i add two task as shown below
dispatch_queue_t serial = dispatch_queue_create("com.apple.serial", DISPATCH_QUEUE_SERIAL);
**//Task 1**
dispatch_async(serial, ^{
[NMUserAPIManager getUserProfileData:^(NMUser *objUser) {
NSLog(#"Get User Profile .....");
_objUser = objUser;
}];
});
**//Task 2**
dispatch_async(serial, ^{
[NMUserAPIManager getUserRecentTransactionData:^(NSDictionary *responseDictionary) {
_accountTableView.hidden = NO;
[self recentTransactionSetup:responseDictionary];
NSLog(#"Get User Recent transaction");
dispatch_async(dispatch_get_main_queue(), ^{
[self reloadTableData];
});
}];
});
Inside that two task i am calling web service with NSURLSession. Problem is that before my Task 1 completion handle Task2 completion handle get called. According to theory by using serial queue each task waits for the previous task to finish before being executed. It my understanding is correct.
NSURLSession's already run on a background thread, so the issue you are seeing here is that as far as your serial queue is concerned once you call 'getUserProfileData:' technically the work for that block in your queue is finished because the NSURLSession is running on a different thread. If your main goal here is to simply call your second task after your first one completes I don't think you need your own queue you would probably be better off simply doing something like:
[NMUserAPIManager getUserProfileData:^(NMUser *objUser) {
NSLog(#"Get User Profile .....");
_objUser = objUser;
[self getUserTransactions];
}];
-(void)getUserTransactions
{
[NMUserAPIManager getUserRecentTransactionData:^(NSDictionary *responseDictionary) {
_accountTableView.hidden = NO;
[self recentTransactionSetup:responseDictionary];
NSLog(#"Get User Recent transaction");
dispatch_async(dispatch_get_main_queue(), ^{
[self reloadTableData];
});
}];
}
EDIT:
If you are looking for something a little more robust I would check out this post for how you can subclass NSOperation to make your own Asynchronous Operation which you can then use with an NSOperationQueue.
I have to run a method with block, several times inside a for loop.
I also have to wait until all the blocks execution completes.
My problem is that I can't understand what I do wrong, that causes my entire app to freeze. Here is the code:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);//1 - creating semaphore
for(int i = 0; i< myObj.count; i++){
[[DataManager shared] verifyObjectId:myObj[i].id
completionBlock:^(BOOL found) {
if(found){
//code here
dispatch_semaphore_signal(semaphore);//3 - signaling semaphore to continue
}
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//2 - getting semaphore to wait
}
//I want to continue once all DB checks complete
Now, I don't understand, why the semaphore won't release, and the for loop won't continue.
What I actually need, is for the semaphore to release after all the DB checks complete. Ideally, I would want the semaphore to wait outside the for loop. Any suggestions on how to accomplish this?
EDIT: SOLUTION: (based on the accepted answer)
// create a group
dispatch_group_t group = dispatch_group_create();
for(int i = 0; i< myObj.count; i++){
// pair a dispatch_group_enter for each dispatch_group_leave
dispatch_group_enter(group);
[[DataManager shared] verifyObjectId:myObj[i].id
completionBlock:^(BOOL found) {
if(found){
//code here
}
dispatch_group_leave(group); //1 leave
}];
//Get a notification on a block that will be scheduled on the specified queue
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(#"-all done!-");
//code here
});
}
Without access to verifyObjectid:completiongBlock:, there are a couple of issues. First, you only call dispatch_semaphore_signal if found is true. If found is every false, you'll deadlock. That may just be a transcription error and your real code might not do that.
Another guess is that the completion block is being submitted to the queue that you're currently running on (the main queue?) If that's true, then that would definitely be a deadlock, because you'll never run dispatch_semaphore_signal since it's waiting on dispatch_semaphore_wait. I can't tell without information about DataManager.
Your approach also serializes the calls, whereas I think you wanted them to be in parallel. Each call has to wait for the former one to finish in your code.
The better tools to use here are dispatch_apply and dispatch_group. Something like this (untested):
dispatch_group_t group = dispatch_group_create();
dispatch_apply(myObj.count, dispatch_get_global_queue(0, 0), ^(size_t i){
dispatch_group_enter(group);
[[DataManager shared] verifyObjectId:myObj[i].id
completionBlock:^(BOOL found) {
if(found){
//code here
}
dispatch_group_leave(group));
}];
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_apply won't return until all the blocks have completed running, which means that dispatch_group_enter has run "count" times. You then use dispatch_group_wait to wait for all the calls to dispatch_group_leave.
I have developed the following method, which checks the app's ability to communicate with the server.
The method performs a simple query and knows that if it gets a result, the app should be connected (basic ping mechanism).
- (BOOL)isAppConnected
{
__block BOOL isConnected = NO;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[[SFRestAPI sharedInstance] performSOQLQuery:#"SELECT id FROM Account LIMIT 1"
failBlock:^(NSError *e) {
isConnected = NO;
NSLog(#"NOT CONNECTED %#", e);
NSLog(#"fail block ON THE MAIN THREAD? %hhd", [NSThread isMainThread]);
dispatch_semaphore_signal(semaphore);
} completeBlock:^(NSDictionary *dict) {
isConnected = YES;
NSLog(#"%#", dict);
NSLog(#"complete block ON THE MAIN THREAD? %hhd", [NSThread isMainThread]);
dispatch_semaphore_signal(semaphore);
}];
// if the wait times-out we will receive a non-zero result and can assume no connection to SF
//When using: DISPATCH_TIME_FOREVER the app hangs forever!!
int waitResult = dispatch_semaphore_wait(semaphore, 30 * NSEC_PER_SEC);
NSLog(#"waitResult: %d", waitResult);
return isConnected;
}
I am using the 'dispatch_semaphore_wait' as suggested in the Apple documentation
My goal is to wait on the response or a short timeout to figure out if we really have a valid connection.
With the code above, 'dispatch_semaphore_wait' never actually waits, i.e. execution does not stop at that line but it continues immediately (always returning 49 as the result to the dispatch_semaphore_wait call). That is unless I use DISPATCH_TIME_FOREVER in which case the app hangs forever...
At the moment I am calling this method from the main thread. I am aware that this is a bad idea, but I wanted to see this working as expected before refactoring.
What could be causing this behaviour?
Thanks.
The parameter for dispatch_semaphore_wait is not a delay, but the time when the semaphore should wake up. Yours will wake up 30 seconds after Midnight, Jan 1st. 1970 (or 2001, not sure). Use the dispatch_time function.
- (BOOL)isAppConnected
{
__block BOOL isConnected = NO;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
// Add this code...
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[SFRestAPI sharedInstance] performSOQLQuery:#"SELECT id FROM Account LIMIT 1"
failBlock:^(NSError *e) {
isConnected = NO;
NSLog(#"NOT CONNECTED %#", e);
NSLog(#"fail block ON THE MAIN THREAD? %hhd", [NSThread isMainThread]);
dispatch_semaphore_signal(semaphore);
} completeBlock:^(NSDictionary *dict) {
isConnected = YES;
NSLog(#"%#", dict);
NSLog(#"complete block ON THE MAIN THREAD? %hhd", [NSThread isMainThread]);
dispatch_semaphore_signal(semaphore);
}];
});
int waitResult = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(#"waitResult: %d", waitResult);
return isConnected;
}
First of all if your fail and complete block are invoked on the main thread then you are going to wait forever, or until the timeout you specify 'times out'
The reason is because the main thread starts waiting after you call dispatch_semaphore_wait. And then if your performSOQLQuery calls the blocks on the main thread nothing will happen until the time out 'times out'.
Now of coarse if you specify a time out of forever the semaphore will never signal or let go, which means your main thread will wait forever, for itself.
Change the waiting code to this: never make the main thread wait
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
int waitResult = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(#"waitResult: %d", waitResult);
});
And do not return a Bool as you are doing, since this is a lengthy operation, you want to use a block that has a result.
Also make sure that your performSOQLQuery() method is not on the main thread please.
On a Stackoverflow question (see "Source 1" at the bottom), I see the following pattern when working with AddressBook in which the programmer wants to block the main thread until the user has granted (or denied) access to his/her AddressBook:
ABAddressBookRef addressBook = ABAddressBookCreate();
__block BOOL accessGranted = NO;
if (ABAddressBookRequestAccessWithCompletion != NULL) { // we're on iOS 6
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
accessGranted = granted;
dispatch_semaphore_signal(sema);
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_release(sema);
}
else { // we're on iOS 5 or older
accessGranted = YES;
}
if (accessGranted) {
NSArray *thePeople = (__bridge_transfer NSArray*)ABAddressBookCopyArrayOfAllPeople(addressBook);
// Do whatever you need with thePeople...
}
but according to the documentation (ABAddressBook Reference), the completion handler is executed on an arbitrary queue, which means it could be executed on the main queue. If the completion handler were placed on the main queue, then the above code would result in a deadlock. Is there any documentation guaranteeing that the completion handler will not be placed on the main queue?
ABAddressBook Reference: "The completion handler is called on an arbitrary queue. If your app uses an address book throughout the app, you are responsible for ensuring that all usage of that address book is dispatched to a single queue to ensure correct thread-safe operation."
Source:
First answer to: How do I correctly use ABAddressBookCreateWithOptions method in iOS 6?
This is essentially what I'm doing to run an asynchronous method synchronously:
This essentially works when called once, but when called multiple times, it will eventually stay inside the while loop and never get signaled. Any ideas on how to set a timer to eventually time out after sometime?
__block SomeClass *result = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0UL);
dispatch_async(queue, ^{
[[SomeManager sharedInstance] someMethodWithCallback:^(id responseObject, NSError *error) {
if (!error) {
result = (SomeClass *)ResponseObject;
}
dispatch_semaphore_signal(semaphore);
}];
});
// wait with a time limit
while (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW)) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0]];
}
dispatch_release(semaphore);
Thanks
That looks kind of like GCD abuse to me. ;) Are you running the run loop because this is executing on the main thread? Why not just use a dispatch_async() from your completion handler to invoke a handler on the main thread? eg:
- (void)handleDataReady: (id) results error: (NSError *) error {
// update your app
}
- (void)performAsyncUpdate {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0UL);
dispatch_async(queue, ^{
[[SomeManager sharedInstance] someMethodWithCallback:^(id responseObject, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
[self handleDataReady:responseObject error:error];
}];
});
}
If you really want to make it synchronous, i.e. blocking the calling thread until the operation completes then use the following pattern (of course you want to avoid blocking threads if possible)
NSCondition *waitCondtion = [NSCondition new];
__block BOOL completed = NO;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0UL);
dispatch_async(queue, ^{
[[SomeManager sharedInstance] someMethodWithCallback:^(id responseObject, NSError *error) {
if (!error) {
result = (SomeClass *)ResponseObject;
}
[waitCondtion lock];
completed = YES;
[waitCondition signal];
[waitCondition unlock];
}];
});
[waitCondtion lock];
if (!completed)
[waitCondtion wait];
[waitCondition unlock];
You can also use "waitUntilDate:" to timeout the wait after a period.
However, this pattern only works as long as the "someMethodWithCallback does not call its callback block on the same thread that is being blocked. I have copied your code because it is not obvious how "someMethodWithCallback" is implemented. Since this method is using an asynchronous pattern, then it must be doing something asynchronously therefore why are you calling it inside a dispatch_async? What thread will it call its callback block on?
You should "fill" the completion handler with whatever code you require to process the result when the completion handler finished (and also completely removing that run loop).
In order to "abort" an asynchronous operation, you should provide a cancel message which you send the asynchronous result provider.
In your case, since you have a singleton, the cancel message would have to be send like this:
[[SomeManager sharedInstance] cancel];
When the operation receives the cancel message, it should as soon as possible abort its task and call the completion handler with an appropriate NSError object indicating that it has been cancelled.
Note, that cancel messages may be asynchronous - that means, when it returns, the receiver may still execute the task.
You may achieve a "timeout" with setting up a timer, which sends the cancel message the operation, unless it has been invalidated when the operation finished.