2

在线程 AI 中调用一个异步服务,该服务在线程 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,而load由线程 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);
} 

我怎样才能让它工作?

4

4 回答 4

2

似乎有几个问题:

  1. 在您的NSCondition示例中,正在执行锁定,并且在设置一些状态变量load之前不会解锁。responseresponse不可能做到这一点,因为它也在尝试做一个锁(根据锁的性质,它会阻塞,直到另一个线程释放它的锁)。

  2. 此外,load正在发起一个service请求(您尚未与我们分享其详细信息),但根据您描述的行为(即看不到“log2”),我猜测该服务请求计划在同一线程为load. (通常,如果服务在其他一些运行循环/队列/线程上运行,您会在该服务启动期间看到一个参数,这将使这一点变得明确。)但是如果load被冻结,等待来自另一个线程的信号,则服务不会开始。您必须分享service请求性质的一些细节,以便我们进一步评论。

  3. 在评论中,您描述了使用GDataServiceGoogle. 在您最初的问题中,您建议此服务在单独的线程上运行。但是当我查看他们的一个示例应用程序时,我在NSURLConnectionDataDelegate方法中放置了一个断点,它在主线程上被调用(实际上,它使用“当前”线程,并且因为示例从主线程启动它,调用在NSURLConnectionDataDelegate主线程上)。这证实了我之前的观点。

    (顺便说一句,这并不罕见。许多NSURLConnectionDataDelegate基于实现的实现使用主队列进行网络连接。我对这种做法并不感到疯狂,但它使他们不必为网络活动创建另一个运行循环。他们是只是假设你不会阻塞主队列。)

    但是,如果您在调用服务的线程上有一些锁或信号量,这将阻止NSURLConnectionDataDelegate方法被调用,因此您response传递的方法didFinishSelector将永远不会被调用。僵局。

  4. 但是,听起来您发现了另一个问题,即从您发起服务调用NSOperation将导致服务的内部NSURLConnectionDataDelegate调用不会被调用。这是来自后台队列的调用的常见问题NSURLConnection,通常通过以下两种方式解决: (a) 在具有自己的运行循环的专用线程中调度网络连接;(b) 在NSURLConnection中调度[NSRunLoop mainRunLoop];或 (c) 为操作创建自己的运行循环。而且您已经成功地确定,由于此 GDataServiceGoogle 服务没有公开用于控制使用哪个运行循环的接口,因此您必须选择选项 (c)。这可能是最不优雅的解决方案,但考虑到 GDataServiceGoogle 的限制,它可能是你能做的最好的。

你问:

我怎样才能让它工作?

虽然我在下面描述了几个解决方案,但最关键的观察是您根本不应该使用信号量、锁或紧密while循环。这些都代表了对处理这些异步请求的正确方法的误解:不是“等待”服务请求完成,而是在它完成时通知您(当您的response方法将被调用时)。删除所有信号量、锁和紧密while循环,并在“log3”中移动您想要执行的任何逻辑并将其放入response方法中。

有了这些,更一般地考虑死锁,有几个观察结果:

  1. 确保将您的服务安排在某个不会锁定的线程上。例如,您经常会看到网络服务将在其上运行的第三个服务专用线程/队列。或者有些人会将网络内容安排在主线程上(你永远不会阻塞),尽管我更喜欢这种东西的专用线程。或者我看到有些人实际上在load例程中调用了执行 runloop(但我认为这是一种可怕的做法)。但是,您根本不能load执行阻塞等待或其他功能并让它在同一个线程上运行您的服务,因此请确保该服务在具有自己的运行循环的其他线程上运行。

  2. 如果您打算使用锁来同步对关键变量的访问,请确保两个线程都没有正当理由长时间持有锁。尝试将锁的持续时间(如果有)最小化到尽可能小的代码部分,即那些您需要更新一些相互访问的资源的时间,但尽快释放该锁。但是锁定诸如循环dispatch_semaphore_wait或永久while循环之类的东西通常会出现问题。

  3. 更根本的是,您可能会问是否有可能重构代码以完全消除锁和信号量。(请参阅并发编程指南中的消除基于锁的代码。 )有时这不切实际,但串行队列(或并发队列上的屏障)已经消除了我过去依赖锁和信号量的许多情况。

就像我上面所说的,我相信正确的解决方案是摆脱“等待服务完成”模型,而只依靠服务在完成后调用您的response方法。死锁问题消失了,您的代码效率更高。

于 2013-08-24T04:55:51.390 回答
0

您正在寻找的东西称为信号量。这是插入该术语的问题的链接:Objective-C Sempaphore Discussion

顺便说一句,“信号量”这个词的意思是红绿灯。

于 2013-08-23T16:35:49.027 回答
0

或者,您可以使用信号量来实现此功能。逻辑非常简单:稍等片刻,在线程 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]];
}
于 2013-08-23T16:42:11.373 回答
0

只需在任何全局队列上使用简单的 dispatch_sync,将块与您的服务逻辑一起传递

于 2013-10-23T16:31:14.817 回答