如何让线程A等到线程B完成,然后继续线程A?

时间:2013-08-23 16:31:36

标签: ios objective-c multithreading

在一个线程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);
} 

我怎样才能让它发挥作用?

4 个答案:

答案 0 :(得分:2)

似乎有几个问题:

  1. NSCondition示例中,load正在执行锁定,并且在response设置某个状态变量之前不会解锁。但response可能无法实现,因为它也试图锁定(根据锁的性质,它将阻塞,直到另一个线程释放其锁定)。 / p>

  2. 此外,load正在发起service请求(您尚未与我们分享其详细信息),但会根据您描述的行为(即看不到&#) 34; log2"),我猜这个服务请求被安排在与load相同的线程上运行。 (通常,如果某个服务正在某个其他的runloop / queue / thread上运行,那么您在该服务启动期间会看到一个可以使其显式化的参数。)但如果load被冻结,则等待来自另一个线程的信号,服务不会开始。您必须分享service请求的性质的一些详细信息,以便我们对此进行进一步评论。

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

    (顺便说一下,这并不奇怪。很多基于NSURLConnectionDataDelegate的实现都使用主队列进行网络连接。我对这种做法并不疯狂,但它节省了他们不得不为网络活动创建另一个runloop。他们只是假设你不会阻止主队列。)

    但是如果你在调用服务的线程上有一些锁或信号量,那么就会阻止调用NSURLConnectionDataDelegate方法,从而阻止你传递的response方法为{{ 1}}永远不会被调用。僵局。

  4. 但是,听起来您发现了另一个问题,即从您的didFinishSelector发起服务电话会导致服务的内部NSOperation来电赢得'被叫。这是来自后台队列的NSURLConnectionDataDelegate调用的常见问题,通常通过(a)在具有自己的运行循环的专用线程中调度网络连接来解决; (b)安排NSURLConnection中的NSURLConnection;或(c)为操作创建自己的运行循环。并且您已成功识别出因为此GDataServiceGoogle服务未公开用于控制使用哪个运行循环的界面,您必须选择(c)选项。它可能是最不优雅的解决方案,但考虑到GDataServiceGoogle的限制,它可能是您可以做的最好的。

  5. 你问:

      

    我怎样才能让它发挥作用?

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

    有了我们,在更普遍地考虑死锁的情况下,有一些观察结果:

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

    2. 如果您要使用锁来同步对关键变量的访问,请确保没有任何线程在没有充分理由的情况下长时间保持锁定。尝试将锁的持续时间(如果有的话)最小化到代码的最小可能部分,即只需要更新某些相互访问的资源的那些时间,但尽快释放该锁。但锁定诸如load或永久dispatch_semaphore_wait循环之类的内容通常会有问题。

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

    4. 就像我上面所说的那样,我认为正确的解决方案是远离"等待服务完成"模型,只需依靠服务就可以在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,使用服务逻辑传递块