ios - 并发操作、NSOperationQueue 和异步 API 所需的说明

标签 ios objective-c asynchronous nsoperation nsoperationqueue

这是一个两部分的问题。希望有人能回复完整的答案。
NSOperation s 是强大的对象。它们可以是两种不同的类型:非并发或并发。

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

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

If you want to implement a concurrent operation—that is, one that runs asynchronously with respect to the calling thread—you must write additional code to start the operation asynchronously. For example, you might spawn a separate thread, call an asynchronous system function, or do anything else to ensure that the start method starts the task and returns immediately and, in all likelihood, before the task is finished.



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

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

Note: In OS X v10.6, operation queues ignore the value returned by isConcurrent and always call the start method of your operation from a separate thread.



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

然后,在这个帖子 Concurrent Operations ,并发操作用于通过NSURLConnection下载一些HTTP内容。 (以其异步形式)。操作是并发的并包含在特定队列中。
UrlDownloaderOperation * operation = [UrlDownloaderOperation urlDownloaderWithUrlString:url];
[_queue addOperation:operation];

NSURLConnection需要循环运行,作者分流了start主线程中的方法(所以我想将操作添加到队列中,它产生了一个不同的队列)。通过这种方式,主运行循环可以调用包含在操作中的委托(delegate)。
- (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 提供的线程机制?

希望这很清楚。

最佳答案

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

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

start方法在并发操作中完成,没有什么特别的事情发生(这是重点)。队列将允许您的操作永远运行,直到您设置 isFinishedYES并执行正确的 KVO willChange/didChange 调用,无论调用线程如何。通常,您会创建一个名为 finish 的方法。这样做,看起来你有。

所有这些都很好,但是如果您需要观察或操作运行操作的线程,则需要注意一些问题。要记住的重要一点是:不要弄乱 GCD 管理的线程。您不能保证它们会在当前执行帧之后存活,并且您绝对不能保证后续的委托(delegate)调用(即来自 NSURLConnection )将发生在同一线程上。事实上,他们可能不会。

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

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

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

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

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

关于ios - 并发操作、NSOperationQueue 和异步 API 所需的说明,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14232908/

相关文章:

ios - 软件导致连接中止。回复中返回错误 :Connection invalid

javascript - 如何将变量的当前值发送到异步回调

c# - 将 IDictionary<T, Task<bool>> 转换为 IObservable<KeyValuePair<T, bool>>

android - 改造链接可观察对象

objective-c - 将一个类中声明的变量传递给另一个类

ios - 在 Swift 中将 unicode 标量表情符号转换为字符串

ios - 异常 - 发送到实例的无法识别的选择器在调用时被抛出

ios - 为什么当我清除数组时内存没有被释放?

objective-c - Objective C 中的 Sqlite 数据库插入语句

objective-c - 样式 UIButtons - 子类?