3

这是一个两部分的问题。希望有人能给出完整的答案。

NSOperations 是强大的对象。它们可以是两种不同的类型:非并发或并发。

第一种是同步运行的。您可以通过将非并发操作添加到NSOperationQueue. 后者为您创建一个线程。结果在于以并发方式运行该操作。唯一需要注意的是此类操作的生命周期。当它的main方法完成时,它就会从队列中移除。当您处理异步 API 时,这可能是一个问题。

现在,并发操作呢?来自苹果文档

如果要实现并发操作(即相对于调用线程异步运行的操作),则必须编写额外的代码来异步启动操作。例如,您可能会生成一个单独的线程、调用异步系统函数或执行其他任何操作来确保 start 方法启动任务并立即返回,并且很可能在任务完成之前返回。

这对我来说几乎是很清楚的。它们异步运行。但是您必须采取适当的措施来确保他们这样做。

我不清楚的是以下内容。医生说:

注意:在 OS X v10.6 中,操作队列会忽略 isConcurrent 返回的值,并始终从单独的线程调用操作的 start 方法。

它的真正含义是什么?如果我在 a 中添加并发操作会发生什么NSOperationQueue

然后,在这篇文章并发操作中,并发操作用于通过NSURLConnection(以异步形式)下载一些 HTTP 内容。操作是并发的并包含在特定队列中。

UrlDownloaderOperation * operation = [UrlDownloaderOperation urlDownloaderWithUrlString:url];
[_queue addOperation:operation];

由于NSURLConnection需要一个循环来运行,作者start在主线程中分流了该方法(所以我想将操作添加到它产生的队列中)。以这种方式,主运行循环可以调用包含在操作中的委托。

- (void)start
{
    if (![NSThread isMainThread])
    {
        [self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO];
        return;
    }

    [self willChangeValueForKey:@"isExecuting"];
    _isExecuting = YES;
    [self didChangeValueForKey:@"isExecuting"];

    NSURLRequest * request = [NSURLRequest requestWithURL:_url];
    _connection = [[NSURLConnection alloc] initWithRequest:request
                                                  delegate:self];
    if (_connection == nil)
        [self finish];
}

- (BOOL)isConcurrent
{
    return YES;
}

// delegate method here...

我的问题如下。这个线程安全吗?运行循环侦听源,但调用的方法在后台线程中调用。我错了吗?

编辑

根据 Dave Dribin 提供的代码,我自己完成了一些测试(参见1)。正如您所写,我注意到NSURLConnection主线程中调用了 的回调。

好的,但现在我仍然很困惑。我会试着解释我的怀疑。

为什么在并发操作中包含在主线程中调用其回调的异步模式?将方法分流start到主线程允许在主线程中执行回调,那么队列和操作呢?我在哪里可以利用 GCD 提供的线程机制?

希望这很清楚。

4

1 回答 1

9

这是一个很长的答案,但简短的版本是您所做的一切都很好并且线程安全,因为您已强制操作的重要部分在主线程上运行。

您的第一个问题是,“如果我在 NSOperationQueue 中添加并发操作会发生什么?” 从 iOS 4 开始NSOperationQueue在幕后使用 GCD。当您的操作到达队列顶部时,它会被提交给 GCD,GCD 会管理一个私有线程池,该线程池会根据需要动态增长和收缩。GCD 分配其中一个线程来运行start您操作的方法,并保证该线程永远不会成为主线程。

当该start方法在并发操作中完成时,没有什么特别的事情发生(这就是重点)。无论调用线程如何,队列都将允许您的操作永远运行,直到您设置isFinished并执行正确的 KVO willChange/didChange 调用。YES通常,您会调用一个方法finish来执行此操作,看起来就像您拥有的那样。

所有这一切都很好,但是如果您需要观察或操作运行您的操作的线程,则需要注意一些事项。要记住的重要一点是:不要乱用 GCD 管理的线程。你不能保证它们会超过当前的执行帧,你也绝对不能保证后续的委托调用(即 from NSURLConnection)会在同一个线程上发生。事实上,他们可能不会。

在您的代码示例中,您已分流start到主线程,因此您无需过多担心后台线程(GCD 或其他)。当您创建NSURLConnection它时,它会在当前运行循环上调度,并且它的所有委托方法都将在该运行循环的线程上调用,这意味着在主线程上启动连接可以保证它的委托回调也发生在主线程上。从这个意义上说,它是“线程安全的”,因为除了操作本身之外,后台线程上几乎没有实际发生任何事情,这实际上可能是一个优势,因为 GCD 可以立即回收线程并将其用于其他用途。

让我们想象一下,如果你不强制start在主线程上运行,而只是使用 GCD 给你的线程会发生什么。如果它的线程消失了,比如当它被 GCD 回收到它的私有池中时,运行循环可能会永远挂起。有一些技术可以使线程保持活动状态(例如添加一个空的NSPort),但它们不适用于 GCD 创建的线程,仅适用于您自己创建的线程并且可以保证其生命周期。

这里的危险在于,在轻负载下,您实际上可以在 GCD 线程上运行运行循环并认为一切都很好。一旦你开始运行许多并行操作,尤其是当你需要在飞行中取消它们时,你将开始看到永远不会完成也永远不会释放的操作,从而导致内存泄漏。如果您想完全安全,则需要创建自己的专用NSThread并保持运行循环永远运行。

在现实世界中,做你正在做的事情并在主线程上运行连接要容易得多。管理连接消耗的 CPU 非常少,并且在大多数情况下不会干扰您的 UI,因此完全在后台运行连接几乎没有什么好处。主线程的运行循环总是在运行,你不需要去搞乱它。

但是,可以NSURLConnection使用上述专用线程方法完全在后台运行连接。例如,查看JXHTTP,特别是JXOperationJXURLConnectionOperation类

于 2013-01-11T23:51:17.450 回答