Reputation: 1521
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
Reputation: 848
Just use simple dispatch_sync on any global queue, passing block with your service logic
Upvotes: 0
Reputation: 438232
It seems like there are a couple of issues:
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).
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.
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.
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:
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.
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.
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
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
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