主线程的NSRunLoop被一个次要线程所引用

5

我最近一直在研究一个示例应用程序,试图完全理解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下运行。像魔法一样工作。然而,我几乎可以确定,在次要线程中依赖于我的主线程运行循环是错误的。

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

  1. 通过应用我采取的小“hack”来使次要线程运行并响应运行循环中的事件,可能会出现什么问题?

  2. 如何正确配置次要线程以监听所有事件/基于计时器的源,以便无需执行步骤1。

感谢大家的见解。

3个回答

6

逆序回答你的问题:

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

如果运行循环中没有附加输入源或定时器,则此方法立即退出;否则,它通过重复调用runMode:beforeDate:NSDefaultRunLoopMode下运行接收器。换句话说,该方法有效地开始了一个无限循环以处理来自运行循环的输入源和计时器的数据。

因此,使用线程自己的运行循环,可能没有为运行循环定义输入源,所以它会立即返回。如果有,您的运行循环将无限循环,之后的代码将永远不会被执行。

为了使事情正常工作,您的运行循环需要首先有一些输入源,然后需要定期运行以检查事件。请注意,您不希望使用[NSRunLoop run],因为您永远不会收回控制权。相反,我建议在return YES之前设置一个循环,该循环不断运行运行循环,直到线程被取消,或者直到您完成数据流传输。这将允许运行循环按照到达的方式处理数据。类似于这样:

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

[NSRunLoop runUntilDate:]会在指定日期之前处理事件,然后将控制权返回给您的程序,以便您可以自行处理。

1。它能工作是因为您的主线程的运行循环正在周期性地运行,因此事件正在被处理。 但是,如果您的主线程阻塞,则数据将停止到达。 如果主线程正在等待来自第二个线程的数据,则可能会特别糟糕。 此外,NSRunLoop不是线程安全的:

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

Apple的多线程编程指南有一个名为“运行循环管理”的部分,从某种程度上解释了所有这些内容。 这并不是我曾经阅读过的最清晰的文件,但如果您正在使用运行循环,那么这是一个很好的起点。


2

你是否记得运行runloop?

[[NSRunLoop currentLoop] run]

我确实尝试过了,但没有成功。只有在使用[NSRunLoop mainRunLoop]运行时,代码才会执行。 - anon

1
首先,scheduledTimer... 方法应该自动将计时器添加到当前运行循环中。如果您使用 initWithFireDate... 初始化程序创建计时器,则只需要使用 addTimer: 方法。我怀疑两次添加计时器不会引起问题,但这是一种可能性。 NSRunLooprun 方法不应该返回,直到没有更多的事件源或显式退出循环为止。这意味着您需要在调用 run 之前安排任何事件源。将您的 [myRunLoop run] 调用移动到代码示例的最后即可。(同时,显然,消除 return 语句)
总之,如果 [NSRunLoop run] 返回,则表示您没有在其上安排事件源。使用调试器逐步执行代码。如果您看到它通过 [NSRunLoop run] 调用继续进行,那么您可以确定输入源存在问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接