2013-01-09 23 views
3

这是一个两部分问题。希望有人可以回答一个完整的答案。并发操作所需的说明,NSOperationQueue和异步API

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

第一种类型同步运行。您可以通过将其添加到NSOperationQueue中来利用非并发操作。后者为你创建一个线程。结果包括以并行方式运行该操作。唯一的警告是关于这种操作的生命周期。当它的main方法结束时,它将从队列中移除。在处理异步API时,这可能会成为问题。

现在,并发操作呢?从苹果公司的文档

如果你想实现一个并发的操作,也就是说,一个相对于调用线程,你必须写 额外的代码异步运行 异步开始操作。例如, 可能会产生一个单独的线程,调用异步系统功能或执行其他任何操作来确保启动方法启动 任务并立即返回,并且很可能在完成 任务之前。

这对我来说已经很清楚了。他们异步运行。但是你必须采取适当的行动来确保他们这样做。

我不清楚的是以下几点。医生说:

注:在OS X v10.6中,操作队列忽略 isConcurrent返回并随时调用从 单独的线程的操作启动方法的价值。

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

然后,在此帖子Concurrent Operations中,并发操作用于通过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提供的线程机制?

希望这是明确的。

回答

8

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

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

start方法在并发操作中完成时,没有什么特别的事情发生(这是关键)。队列将允许您的操作永远运行,直到您将isFinished设置为YES,并执行正确的KVO willChange/didChange调用,而不管调用线程如何。通常情况下,您会制作一个名为finish的方法来做到这一点,它看起来像您一样。

所有这一切都很好,但如果您需要观察或操纵正在运行的操作的线程,则需要注意一些注意事项。需要记住的重要一点是:不要与GCD管理的线程混淆。您无法保证他们会超出当前的执行框架,并且您绝对不能保证随后的代表调用(即从NSURLConnection)将在同一个线程上发生。事实上,他们可能不会。

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

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

这里的危险是,在轻负载情况下,您实际上可以在GCD线程上运行一个运行循环,并认为一切都很好。一旦开始运行许多并行操作,特别是如果您需要在midflight中取消它们,您将会看到从未完成且永不释放内存泄漏的操作。如果您想要完全安全,您需要创建自己的专用NSThread并保持运行循环一直持续。

在现实世界中,更容易做你正在做的事情,只需在主线程上运行连接。管理连接消耗的CPU非常少,并且在大多数情况下不会干扰用户界面,所以通过在后台完全运行连接几乎没有什么收获。主线程的运行循环始终运行,您不需要惹恼它。

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

+0

roland,首先,感谢您的回复。这里有很多概念。 +1为您提供支持。我对使用并发操作和异步模式(如NSURLConnection)的方法存在一些疑问。如果在主线程中调用了'NSURLConnection'的回调函数,那么使用两者的优点是什么?我怎样才能利用* CDG *?看我的编辑。再次感谢你。 –

+1

'NSOperation'是封装任何长期运行的可并行任务的自然方式(不管线程优势如何)。 'NSURLConnection'通过它的委托方法(在哪里放置响应数据,如何响应重定向等)为你提供很多控制,但它们是异步的,所以如果你想在'NSOperation'中使用它们,这意味着它有是并发的(否则该操作将不会超过其“开始”方法的退出并继续等待委托回调)。 – roland

+1

另一个主要优势是,它允许您随时取消连接(例如,如果您正在加载用户刚刚解雇的视图的图像),这是通过Cocoa的其他连接技术无法实现的。你甚至可以重写'NSOperation'的'cancel'方法并同时'取消'NSURLConnection'。 – roland