Scholle
Scholle

Reputation: 1521

How to let a thread A wait until thread B is finished, then continue thread A?

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?

Upvotes: 2

Views: 804

Answers (4)

Yurii Romanchenko
Yurii Romanchenko

Reputation: 848

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

Upvotes: 0

Rob
Rob

Reputation: 438232

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.

Upvotes: 2

Lucas Eduardo
Lucas Eduardo

Reputation: 11675

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]];
}

Upvotes: 0

Metaphor
Metaphor

Reputation: 6415

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.

Upvotes: 0

Related Questions