dispatch_semaphore_wait does not wait on semaphore - ios

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.

Related

Dispatch Queue and NSOperation queue

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.

ObjectiveC waiting on for loop blocks with semaphore

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.

Wait for async blocks completion

For example I have a method with three async blocks. Each block result is needed to perform next block to achieve final methods result. So, what I'm looking for is a nice GCD strategy to make'em perform in a strict order and without dead locks
__block id task1Result;
__block id task2Result;
__block id finalResult;
[self startTask1:^(id result) { task1Result = result }]
[self startTask2:task1Result block:^(id result) { task2Result = result }]
[self startTask3:task2Result block:^(id result) { finalResult = result }]
UPD. I have found a solution:
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
__block id task1Result;
__block id task2Result;
__block id finalResult;
[self startTask1:^(id result) {
task1Result = result;
dispatch_semaphore_signal(sem);
}];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
[self startTask2:task1Result block:^(id result) {
task2Result = result;
dispatch_semaphore_signal(sem);
}];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
[self startTask3:task2Result block:^(id result) { finalResult = result }];
But in my case I faced a problem with some library method which brings app to deadlock. ><
Create a serial dispatch queue like described here:
https://developer.apple.com/library/ios/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html
In a nutshell:
dispatch_queue_t queue;
queue = dispatch_queue_create("com.example.MyQueue", NULL);
dispatch_async(queue, ^{
printf("Do some work here.\n");
});
dispatch_async(queue, ^{
printf("When finished do next task.\n");
});
Be aware that you have to handle the queue yourself.
If each task directly consumes the result of the previous task, can’t you start each one from the completion callback of its predecessor? You’ll still need a dispatch group to wait for the last task to complete, though.
dispatch_group_t group = dispatch_group_create();
__block id result;
dispatch_group_enter(group);
[self startTask1:^(id task1Result) {
[self startTask2:task1Result block:^(id task2Result) {
[self startTask3:task2Result block:^(id finalResult) {
result = finalResult;
dispatch_group_leave(group);
}];
}];
}];
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
One complication you may run into is whether attempting to enqueue tasks from a completion handler runs the risk of deadlock, i.e. if your completion handlers are invoked on the same serial queue that handles enqueueing tasks.

How to make particular part of function wait in iOS

I'm trying to do a share operation where I call a function with async block but in my next if statement I need to get the value which is completed in the block to continue. This is my code which will highlight more detail. I heard about NSLock and tried using it but it didnt work, may be I'm doing something lock, I'm not much familiar with locks.
-(void) shareOperation
{
__block NSString *resultText;
BOOL continueSharing;
NSLock *conditionLock=[[NSLock alloc] init];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
[conditionLock lock];
[self performSomeAsynchronousOperation completionBlock:^(NSError *tError, bool status)
{
dispatch_async(dispatch_get_main_queue(),
^{
if (status)
{
resultText = #"Operation completed. Would you like to continue?";
}
else
{
resultText = #" Operation failed. Would you still like to continue?";
}
UIAlertView *result = [UIAlertView alertViewWithTitle:nil message:resultText cancelButtonTitle:#"Cancel" otherButtonTitles:[NSArray arrayWithObjects:#"OK",nil] onDismiss:^(int buttonIndex)
{
NSLog(#"selected button index: %d",buttonIndex);
if (buttonIndex == 0)
{
continueSharing=YES;
[conditionLock unlock];
NSLog(#"We are continuing sharing :)");
}
}onCancel:^{
continueSharing=NO;
[conditionLock unlock];
NSLog(#"cancelled");
}]; [result show];
});
}];
});
}
//should continue only after earlier if block finishes, ie after getting the continueSharing value
if (continueSharing)
{
[self performSomeAnotherAsynchronousOperation];
}
}
Rather than using locks (which are only designed to ensure that there is not simultaneous access to some shared resource), you could use semaphores (which are designed so that one thread can wait for a signal from another thread, which is needed here).
So, you should create a semaphore:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
The alert view completion blocks would signal that semaphore:
dispatch_semaphore_signal(semaphore);
And where you want to wait for that signal (before performSomeAnotherAsynchronousOperation):
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
I tweaked your code a bit, but most notably, changed it so that it would not block the main queue (which you never want to do) by making sure the dispatch_semaphore_wait is done in a background queue. Also note that the dispatch_semaphore_signal is not inside the if statement. This resulted in:
- (void)shareOperation
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__block BOOL continueSharing = NO;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self performSomeAsynchronousOperationWithCompletionBlock:^(NSError *tError, bool status){
dispatch_async(dispatch_get_main_queue(),^{
NSString *resultText;
if (status)
resultText = #"Operation completed. Would you like to continue?";
else
resultText = #"Operation failed. Would you still like to continue?";
UIAlertView *alertView = [UIAlertView alertViewWithTitle:nil message:resultText cancelButtonTitle:#"Cancel" otherButtonTitles:#[#"OK"] onDismiss:^(int buttonIndex) {
NSLog(#"selected button index: %d",buttonIndex);
if (buttonIndex == 0) {
continueSharing = YES;
NSLog(#"We are continuing sharing :)");
}
dispatch_semaphore_signal(semaphore);
} onCancel:^{
dispatch_semaphore_signal(semaphore);
NSLog(#"cancelled");
}];
[alertView show];
});
}];
// should continue only after earlier if block finishes, ie after getting the continueSharing value
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if (continueSharing)
[self performSomeAnotherAsynchronousOperation];
});
}
Even better, you should not use any blocking mechanism like semaphores (certainly not on the main queue), but rather one should simply have the completion block for the alert view initiate the next step of the process directly, itself. I understand that you say that this is not practical in your scenario, but it is generally the correct way to handle these scenarios.
You can use the delay block
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 0.01 * NSEC_PER_SEC);
dispatch_after(delayTime, dispatch_get_main_queue(), ^(void){
})

How to timeout an asynchronous method when ran synchronously

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.

Resources