2

Within a thread A I call an asynchronous service, which runs in thread B. The service calls a delegate method once finished. I want thread A to wait until thread B finishes. I used NSCondition for this.

This is my set up (skipped the unimportant stuff):

-(void)load
{
    self.someCheckIsTrue = YES;
    self.condition = [[NSCondition alloc] init];
    [self.condition lock];

    NSLog(@"log1");
    Service *service = // set up service
    [service request:url delegate:self didFinishSelector:@selector(response:)];

    while (self.someCheckIsTrue)
        [self.condition wait];

    NSLog(@"log3");
    [self.condition unlock];
}

-(void)response:(id)data
{
    NSLog(@"log2");
    [self.condition lock];
    self.someCheckIsTrue = NO;

    // do something with the response, doesn't matter here

    [self.condition signal];
    [self.condition unlock];
} 

For some reason, only "log1" is printed, neither "log2" nor "log3". I assume it's why the delegate method response is called by the "service thread", which is thread B, whereas load is called by thread A.

I also tried a Semaphore, but doesn't work either. Here is the code:

-(void)load
{        
    NSLog(@"log1");
    Service *service = // set up service

    self.sema = dispatch_semaphore_create(0);
    [service request:url delegate:self didFinishSelector:@selector(response:)];
    dispatch_semaphore_wait(self.sema, DISPATCH_TIME_FOREVER);

    NSLog(@"log3");
}

-(void)response:(id)data
{
    NSLog(@"log2");

    // do something with the response, doesn't matter here

    dispatch_semaphore_signal(self.sema);
} 

How can I get this to work?

Scholle
  • 1,521
  • 2
  • 23
  • 44
  • It looks to me like `service` is just not calling your `response:` method. – Aaron Brager Aug 23 '13 at 16:54
  • not impossible, but hard to tell coz the didFinishSelector is supposed to be called in any case, e.g. when the endpoint is not available... etc. – Scholle Aug 23 '13 at 17:17
  • 1
    Well, if it was called you'd see `log2` at the very least. Have you tried setting any breakpoints? – Aaron Brager Aug 23 '13 at 18:12
  • yeah, thats why i set the "log2" where it is. setting breakpoints to either NSLog(@"log2") or NSLog(@"log3") doesn't leads to anything, the program doesn't move forward... – Scholle Aug 23 '13 at 19:00
  • i used the same service instance/same endpoint in another object and there it works... – Scholle Aug 23 '13 at 19:01
  • Perhaps since `service` is not stored in a property/instance variable, it is being deallocated before it ever calls the completion function. – Aaron Brager Aug 23 '13 at 19:36
  • Never mind, `log3` would be printed if that were true. – Aaron Brager Aug 23 '13 at 19:37
  • Problem relates to this: http://stackoverflow.com/questions/9223537/asynchronous-nsurlconnection-with-nsoperation. Will update article with solution soon. – Scholle Aug 24 '13 at 16:18
  • The code above without the semaphore/locks works when put into the main thread, but doesn't work within an NSOperation implementation, where load is called inside main of NSOperation. – Scholle Aug 24 '13 at 17:01

4 Answers4

2

It seems like there are a couple of issues:

  1. In your NSCondition example, load is doing a lock, and won't unlock until response sets some state variable. But response can't possibly get to that because, it's trying to do a lock, too (which, by the nature of locks, will block until the other thread releases its lock).

  2. Furthermore, load is initiating a service request (whose details you haven't shared with us), but based upon the behavior you describe (namely, not see "log2"), I'm guessing that this service request is scheduled to run on the same thread as load. (Often if a service is running on some other runloop/queue/thread, you'd see an argument during the start of that service that would make this explicit.) But if load is frozen, waiting for a signal from the other thread, the service won't commence. You'd have to share some details of the nature of the service request for us to comment further on that.

  3. In comments, you described using GDataServiceGoogle. In your original question, you suggested that this service was running on a separate thread. But when I looked at one of their sample apps, I put a breakpoint in the NSURLConnectionDataDelegate methods, and it was getting called on the main thread (actually, it uses the "current" thread, and because the sample initiated it from the main thread, the NSURLConnectionDataDelegate calls were on the main thread). This confirms my prior point.

    (By the way, that's not at all unusual. Lots of NSURLConnectionDataDelegate based implementations use the main queue for the network connection. I'm not crazy about that practice, but it saves them from having to create another runloop for network activity. They're just assuming that you wouldn't block the main queue.)

    But if you have some lock or semaphore on the thread from which you invoked the service, that will prevent the NSURLConnectionDataDelegate methods from ever getting called, and thus your response method you passed as didFinishSelector will never get called. Deadlock.

  4. But, it sounds like you identified another problem, which is that initiating the service call from your NSOperation will result in the service's internal NSURLConnectionDataDelegate calls won't get called. That's a common problem with NSURLConnection calls from a background queue, usually solved by either (a) scheduling the network connection in a dedicated thread with its own run loop; (b) scheduling the NSURLConnection in the [NSRunLoop mainRunLoop]; or (c) create your own run loop for the operation. And you've successfully identified that because this GDataServiceGoogle service doesn't expose an interface for controlling which run loop is used, that you'll have to go for option (c). It's probably the least elegant solution, but given the constraints of that GDataServiceGoogle, it may be the best you can do.

You ask:

How can I get this to work?

While I describe a couple of solutions below, the most critical observation is that you should not be using semaphores, locks, or tight while loops at all. These all represent a misconception of the correct way to handle these asynchronous requests: Rather than "waiting" for the service request to complete, you are notified when it's complete (when your response method will be called). Remove all of the the semaphore, locks, and tight while loops, and move whatever logic you wanted to do at your "log3" and put it inside the response method.

With that behind us, considering deadlocks more generically, there are a couple of observations:

  1. Make sure to schedule your service on some thread that won't lock. For example, you'll often see some third service-dedicated thread/queue on which the network service will run. Or some folks will schedule the network stuff on the main thread (which you never block), though I prefer a dedicated thread for this sort of stuff. Or I've seen some people actually put a call to perform the runloop right in the load routine (but I think this is a horrible practice). But, you simply can't have load perform a blocking wait or other function and have it run your service on the same thread, so make sure the service is running on some other thread with its own runloop.

  2. If you're going to use locks to synchronize the access to key variables, make sure that neither thread is holding a lock for a prolonged time without good reason. Try to minimize the duration of the locks (if any) to the smallest possible portions of code, i.e. just those times that you need to update some mutually accessed resource, but release that lock as soon as possible. But having a lock around something like a dispatch_semaphore_wait or a perpetual while loop will often be problematic.

  3. More radically, you might ask whether there's a possibility to refactor the code to eliminate the locks and semaphores altogether. (See Eliminating Lock-Based Code in the Concurrency Programming Guide.) Sometimes that's not practical, but serial queues (or barriers on concurrent queues) have eliminated many situations where I used to rely upon locks and semaphores.

Like I said above, I believe the correct solution is to move away from the "wait for the service to finish" model, and just rely upon the service to call your response method when it's done. The deadlock issues disappear and your code is a lot more efficient.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • The two methods I mentioned above are implemented as a concurrent NSOperation, so load gets called by main of NSOperation and is run in its own thread. As service, I am using a GDataServiceGoogle instance, which seems to use NSOperationQueue/NSOperation internally as well. Obviously, I don't have much control over this. The service itself is not instantiated within the load method, rather it's instantiated during app startup, so within the main thread. Thus, the thread setting up the service is different from the thread executing load (which i can confirm coz i manually implemented the code). – Scholle Aug 24 '13 at 05:35
  • But you are right, it seems that the execution of the service gets blocked somehow. – Scholle Aug 24 '13 at 05:36
  • Problem relates to this: http://stackoverflow.com/questions/9223537/asynchronous-nsurlconnection-with-nsoperation. Will update article with solution soon. – Scholle Aug 24 '13 at 16:15
  • 1
    @Scholle The problem was that your `NSURLConnectionDataDelegate` calls are getting called on your first thread, and attempts to semaphore/lock on that thread caused a deadlock. I confirmed this by looking at some sample `GDataServiceGoogle` code and `didReceiveData` happened on main thread, and hence your deadlock. The solution is to get rid of the semaphores/locks, and move your log3 code into `response` method. See my revised answer. – Rob Aug 24 '13 at 16:43
  • Thanks for your in deep investigations. I understand to get rid of the semaphores/locking code. But what do you mean by "move your log3 code into response method"? – Scholle Aug 24 '13 at 16:51
  • The problem is that the response method gets never called, so moving code into it won't solve the problem. I managed to get the response method called by setting the run loop of the background thread calling the service to infinite (as proposed as a possible solution in the the article i linked above). – Scholle Aug 24 '13 at 16:57
  • 1
    @Scholle let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/36172/discussion-between-rob-and-scholle) – Rob Aug 24 '13 at 17:09
0

The thing you are looking for is called a Semaphore. Here's a link to your question with that term plugged in: Objective-C Sempaphore Discussion

Btw, the word "semaphore" means traffic light.

Community
  • 1
  • 1
Metaphor
  • 6,157
  • 10
  • 54
  • 77
  • i have used semaphore too, but didn't get it to work... i am going to add the code i used above – Scholle Aug 23 '13 at 16:37
0

Alternativaly, you can achieve this funcionality with a semaphore. The logic is pretty simple: with a while, wait in you thread A. And in your thread B, as soon you want the thread A to be released, just call dispatch_semaphore_signal(semaphore);

Here's an example that I used to wait for the callbacks in restkit. You can easily adapt it.

//create the semaphore
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

[objectManager.HTTPClient deletePath:[address addressURL] parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {

      //some code here, executed in background

        dispatch_semaphore_signal(semaphore); //releasing semaphore

    }failure:^(AFHTTPRequestOperation *operation, NSError *error) {

       //some other code here

        dispatch_semaphore_signal(semaphore); //releasing semaphore
    }];

//holds the thread until the dispatch_semaphore_signal(semaphore); is send
while (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW))
{
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]];
}
Lucas Eduardo
  • 11,525
  • 5
  • 44
  • 49
0

Just use simple dispatch_sync on any global queue, passing block with your service logic