主线程的NSRunLoop在辅助线程中被引用

时间:2009-05-29 02:11:50

标签: iphone objective-c cocoa cocoa-touch multithreading

我最近刚试了一个示例应用程序试图让我的头完全缠绕NSRunLoop。我编写的示例通过NSOperation创建了一个简单的辅助线程。辅助线程执行一些任务,例如处理NSTimer以及使用NSStream的一些基本流程。这两个输入源都需要正确配置NSRunLoop才能执行。

我的问题是这个。最初我在辅助线程中有一些看起来像这样的代码:

NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
self.connectTimer = [NSTimer scheduledTimerWithTimeInterval:connectTimeout
                                                     target:self
                                                   selector:@selector(connectionConnectedCheck:)
                                                   userInfo:nil 
                                                    repeats:NO];

[myRunLoop addTimer:self.connectTimer forMode:NSDefaultRunLoopMode]; // added source here
[myRunLoop run];

[NSStream getStreamsToHostNamed:relayHost port:relayPort inputStream:&inputStream outputStream:&outputStream];
if ((inputStream != nil) && (outputStream != nil))
{
    sendState = kSKPSMTPConnecting;
    isSecure = NO;

    [inputStream retain];
    [outputStream retain];

    [inputStream setDelegate:self];
    [outputStream setDelegate:self];

    [inputStream scheduleInRunLoop: myRunLoop //[NSRunLoop currentRunLoop]  
                                    forMode:NSRunLoopCommonModes];
    [outputStream scheduleInRunLoop: myRunLoop //[NSRunLoop currentRunLoop] 
                                    forMode:NSRunLoopCommonModes];

    [inputStream open];
    [outputStream open];

    self.inputString = [NSMutableString string];



    return YES;
}

现在,使用上面的代码,事件永远不会处理。不在currentRunLoop。之后我做了一些可怕的事情,因为这只是一项教育练习,并将其修改为在NSRunLoop mainRunLoop下运行。像魔术一样工作。但是,我几乎可以肯定,根据我的主线程,在辅助线程中运行循环是在错误的的10个不同级别上。

所以我的问题分为两部分,我希望没问题。

  1. 为了让次要线程运行并通过运行循环响应事件,我应用的小'hack'可能出错?

  2. 配置辅助线程以侦听所有基于事件/定时器的源的正确方法是什么,所以我不必执行第1步。

  3. 感谢所有人的洞察力。

3 个答案:

答案 0 :(得分:6)

以相反的顺序回答您的问题:

2。你有两个问题。 -[NSRunLoop run]的文档说:

  

如果没有输入源或计时器   附加到运行循环,这个方法   立即退出;否则,它运行   接收器在   反复NSDefaultRunLoopMode   调用runMode:beforeDate:。其他   言语,这种方法有效地开始   一个处理数据的无限循环   来自运行循环的输入源和   定时器。

因此,使用线程自己的运行循环,可能没有为运行循环定义输入源,因此它会立即返回。如果存在,则运行循环无限循环,并且在该点之后的其余代码永远不会被执行。

为了使事情正常工作,您的运行循环首先需要一些输入源,然后需要定期运行以检查事件。请注意,您不希望使用[NSRunLoop run],因为您永远无法控制回来。相反,我建议在return YES之前设置一个循环,它继续运行运行循环,直到线程被取消,或者直到你完成流数据。这将允许运行循环在数据到达时处理数据。像这样:

while (!done) {
    [myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
}

[NSRunLoop runUntilDate:]处理事件直到指定日期,然后将控制权返回给您的程序,以便您可以随心所欲。

1。这是有效的,因为主线程的运行循环定期运行,因此正在处理事件。但是,如果您的主线程被阻止,您的数据将停止到达。如果主线程正在等待第二个线程的数据,这可能会特别糟糕。此外,NSRunLoop不是线程安全的:

  

警告:NSRunLoop类是   通常不被认为是   线程安全及其方法应该   只能在上下文中调用   当前的线程。你永远不应该   试着调用一个方法   在一个运行的NSRunLoop对象   不同的线程,这样做可能   导致意外的结果。   (来自NSRunLoop文档。)

Apple的线程编程指南有一个名为Run Loop Management的部分,它在某种程度上解释了所有这些。这不是我读过的最清晰的文档,但是如果你正在使用run loop,那么这是一个很好的起点。

答案 1 :(得分:2)

你还记得运行runloop吗?

[[NSRunLoop currentLoop] run]

答案 2 :(得分:1)

首先,scheduledTimer...方法应该自动将计时器添加到当前运行循环中。如果使用addTimer:初始化程序创建计时器,则只需使用initWithFireDate...方法。我怀疑两次添加计时器会导致问题,但这是可能的。

run NSRunLoop方法不应该返回,直到没有更多事件源或显式退出循环。这意味着您需要在调用run之前安排任何事件源。将[myRunLoop run]电话移至代码示例的最后。 (另外,显然,消除return语句)

最重要的是,如果[NSRunLoop run]正在返回,那么您没有安排事件源。使用调试器逐步执行代码。如果您看到它超过了[NSRunLoop run]来电,那么您可以确定输入来源存在问题。