首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >并发操作、NSOperationQueue和异步API所需的澄清

并发操作、NSOperationQueue和异步API所需的澄清
EN

Stack Overflow用户
提问于 2013-01-09 10:16:38
回答 1查看 1.8K关注 0票数 3

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

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

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

那么并发操作呢?苹果公司的

如果您想实现一个并发操作--也就是说,一个针对调用线程异步运行的操作--您必须编写额外的代码来异步启动该操作。例如,您可能会生成一个单独的线程,调用一个异步系统函数,或者做任何其他事情来确保start方法启动任务并立即返回,并且很可能在任务完成之前返回。

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

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

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

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

然后,在本文中的并行操作中,并发操作通过NSURLConnection (以其异步形式)下载某些HTTP。操作是并发的,并包含在特定队列中。

代码语言:javascript
复制
UrlDownloaderOperation * operation = [UrlDownloaderOperation urlDownloaderWithUrlString:url];
[_queue addOperation:operation];

由于NSURLConnection需要一个循环才能运行,所以作者将start方法分流到主线程中(因此我假设将操作添加到它生成的队列中)。以这种方式,主运行循环可以调用操作中包含的委托。

代码语言:javascript
复制
- (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...

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

编辑

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

好吧,但现在我还是很困惑。我会尽力解释我的疑虑。

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

希望这是清楚的。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2013-01-11 23:51:17

这是一个很长的答案,但简短的版本是,您所做的工作是完全安全的,因为您迫使操作的重要部分在主线程上运行。

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

start方法在并发操作中完成时,不会发生任何特殊情况(这就是要点)。队列将允许您的操作永远运行,直到您将isFinished设置为YES,并执行正确的KVO /didChange调用,而不管调用线程如何。通常,您会创建一个名为finish的方法来完成这一任务,它看起来就是这样的。

所有这些都很好,但是如果需要观察或操作正在运行操作的线程,则需要注意一些问题。需要记住的重要一点是:不要弄乱由GCD管理的线程。您不能保证它们将超过当前的执行框架,而且您也肯定不能保证后续的委托调用(即来自NSURLConnection)将发生在同一个线程上。事实上,他们可能不会。

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

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

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

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

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

票数 9
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/14232908

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档