在一个线程A中,我调用一个在线程B中运行的异步服务。该服务在完成后调用一个委托方法。我希望线程A等到线程B完成。我为此使用了NSCondition。
这是我的设置(跳过不重要的东西):
-(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];
}
由于某种原因,只打印“log1”,既不打印“log2”也不打印“log3”。我假设这就是为什么委托方法响应由“服务线程”调用,即线程B,而加载由线程A调用。
我也试过一个信号量,但也没用。这是代码:
-(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);
}
我怎样才能让它发挥作用?
答案 0 :(得分:2)
似乎有几个问题:
在NSCondition
示例中,load
正在执行锁定,并且在response
设置某个状态变量之前不会解锁。但response
可能无法实现,因为它也试图锁定(根据锁的性质,它将阻塞,直到另一个线程释放其锁定)。 / p>
此外,load
正在发起service
请求(您尚未与我们分享其详细信息),但会根据您描述的行为(即看不到&#) 34; log2"),我猜这个服务请求被安排在与load
相同的线程上运行。 (通常,如果某个服务正在某个其他的runloop / queue / thread上运行,那么您在该服务启动期间会看到一个可以使其显式化的参数。)但如果load
被冻结,则等待来自另一个线程的信号,服务不会开始。您必须分享service
请求的性质的一些详细信息,以便我们对此进行进一步评论。
在评论中,您描述了使用GDataServiceGoogle
。在您的原始问题中,您建议此服务在单独的线程上运行。但是当我查看他们的一个示例应用程序时,我在NSURLConnectionDataDelegate
方法中放置了一个断点,并且它在主线程上被调用(实际上,它使用"当前"线程,并且因为样本从主线程启动它,NSURLConnectionDataDelegate
调用在主线程上。这证实了我之前的观点。
(顺便说一下,这并不奇怪。很多基于NSURLConnectionDataDelegate
的实现都使用主队列进行网络连接。我对这种做法并不疯狂,但它节省了他们不得不为网络活动创建另一个runloop。他们只是假设你不会阻止主队列。)
但是如果你在调用服务的线程上有一些锁或信号量,那么就会阻止调用NSURLConnectionDataDelegate
方法,从而阻止你传递的response
方法为{{ 1}}永远不会被调用。僵局。
但是,听起来您发现了另一个问题,即从您的didFinishSelector
发起服务电话会导致服务的内部NSOperation
来电赢得'被叫。这是来自后台队列的NSURLConnectionDataDelegate
调用的常见问题,通常通过(a)在具有自己的运行循环的专用线程中调度网络连接来解决; (b)安排NSURLConnection
中的NSURLConnection
;或(c)为操作创建自己的运行循环。并且您已成功识别出因为此GDataServiceGoogle服务未公开用于控制使用哪个运行循环的界面,您必须选择(c)选项。它可能是最不优雅的解决方案,但考虑到GDataServiceGoogle的限制,它可能是您可以做的最好的。
你问:
我怎样才能让它发挥作用?
虽然我在下面描述了几个解决方案,但最关键的观察是你根本不应该使用信号量,锁或紧[NSRunLoop mainRunLoop]
循环。这些都代表了对处理这些异步请求的正确方法的误解:而不是"等待"要完成服务请求,系统会在完成后通知您(当您调用while
方法时)。删除所有信号量,锁和紧response
循环,并移动你想要做的任何逻辑" log3"并将其放在while
方法中。
有了我们,在更普遍地考虑死锁的情况下,有一些观察结果:
确保在一些不会锁定的线程上安排您的服务。例如,您经常会看到运行网络服务的第三个服务专用线程/队列。或者有些人会在主线程(你永远不会阻止)上安排网络内容,虽然我更喜欢这种东西的专用线程。或者我已经看到有些人实际上在response
例程中调用了执行runloop(但我认为这是一种可怕的做法)。但是,您根本无法load
执行阻塞等待或其他功能并让它在同一个线程上运行您的服务,因此请确保该服务在其他具有自己的runloop的线程上运行。 / p>
如果您要使用锁来同步对关键变量的访问,请确保没有任何线程在没有充分理由的情况下长时间保持锁定。尝试将锁的持续时间(如果有的话)最小化到代码的最小可能部分,即只需要更新某些相互访问的资源的那些时间,但尽快释放该锁。但锁定诸如load
或永久dispatch_semaphore_wait
循环之类的内容通常会有问题。
更根本的是,您可能会问是否有可能重构代码以完全消除锁和信号量。 (请参阅并发编程指南中的Eliminating Lock-Based Code。)有时这不实用,但串行队列(或并发队列上的障碍)已经消除了我过去依赖的许多情况锁和信号量。
就像我上面所说的那样,我认为正确的解决方案是远离"等待服务完成"模型,只需依靠服务就可以在while
方法完成后调用它。死锁问题消失了,你的代码效率更高。
答案 1 :(得分:0)
您正在寻找的东西称为信号量。以下是插入该术语的问题的链接:Objective-C Sempaphore Discussion
顺便说一下,“信号量”这个词意味着交通灯。
答案 2 :(得分:0)
Alternativaly,您可以使用信号量实现此功能。逻辑非常简单:有一段时间,请在线程A中等待。在线程B中,只要您想要释放线程A,只需调用dispatch_semaphore_signal(semaphore);
这是我用来等待restkit中的回调的一个例子。你可以很容易地适应它。
//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]];
}
答案 3 :(得分:0)
在任何全局队列上使用简单的dispatch_sync,使用服务逻辑传递块