NSOperation和NSOperationQueue工作线程与主线程


80

我必须在我的应用程序中执行一系列下载和数据库写入操作。我使用NSOperationNSOperationQueue相同。

这是应用程序场景:

  • 从某个地方获取所有邮政编码。
  • 对于每个邮政编码,请提取所有房屋。
  • 为每个房子获取居民详细信息

如前所述,我NSOperation为每个任务定义了一个。在第一种情况下(Task1),我正在向服务器发送请求以获取所有邮政编码。内的委托NSOperation将接收数据。然后将此数据写入数据库。数据库操作是在不同的类中定义的。从NSOperation类中,我正在调用数据库类中定义的写函数。

我的问题是数据库写操作是否发生在主线程或后台线程中?当我在A中调用它时, NSOperation我期望它在与相同的线程(不是MainThread)中运行NSOperation。有人可以在处理NSOperation和时解释这种情况吗NSOperationQueue


3
如果将操作添加到主队列中,那么它们将在主线程中执行。如果创建自己的NSOperationQueue并向其添加操作,则将在此队列的线程中执行操作。
Cy-4AH 2013年

1
我不认为您会得到比@ Cy-4AH更好的答案,除非您得到更具体的信息/发布一些代码。我会说你可以随时把一个断点在代码中,当跳闸它会告诉你什么线程的跟踪是英寸
布拉德·奥尔雷德

“ NSOperation中的委托将接收数据”的作用是什么。意思?无论是NSOperation也不NSOperationQueue包含委托性质。
Jeffery Thomas

您也可以将委托调用推到主线程上,而不用对当前线程做任何假设……
2013年

Answers:


174

我的问题是数据库写操作是否发生在主线程或后台线程中?

如果您NSOperationQueue从头开始创建,如下所示:

NSOperationQueue *myQueue = [[NSOperationQueue alloc] init];

它将在后台线程中:

操作队列通常提供用于运行其操作的线程。在OS X v10.6和更高版本中,操作队列使用libdispatch库(也称为Grand Central Dispatch)来启动其操作的执行。结果,操作总是在单独的线程上执行,而不管它们被指定为并发操作还是非并发操作

除非您使用mainQueue

NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];

您还可以看到如下代码:

NSOperationQueue *myQueue = [[NSOperationQueue alloc] init];
[myQueue addOperationWithBlock:^{

   // Background work

    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        // Main thread work (UI usually)
    }];
}];

和GCD版本:

dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void)
             {
              // Background work            
             dispatch_async(dispatch_get_main_queue(), ^(void)
              {
                   // Main thread work (UI usually)                          
              });
});

NSOperationQueue可以更好地控制您要执行的操作。您可以在两个操作之间创建依赖关系(下载并保存到数据库)。要在一个块与另一个块之间传递数据,您可以假设例如aNSData将来自服务器,因此:

__block NSData *dataFromServer = nil;
NSBlockOperation *downloadOperation = [[NSBlockOperation alloc] init];
__weak NSBlockOperation *weakDownloadOperation = downloadOperation;

[weakDownloadOperation addExecutionBlock:^{
 // Download your stuff  
 // Finally put it on the right place: 
 dataFromServer = ....
 }];

NSBlockOperation *saveToDataBaseOperation = [[NSBlockOperation alloc] init];
__weak NSBlockOperation *weakSaveToDataBaseOperation = saveToDataBaseOperation;

 [weakSaveToDataBaseOperation addExecutionBlock:^{
 // Work with your NSData instance
 // Save your stuff
 }];

[saveToDataBaseOperation addDependency:downloadOperation];

[myQueue addOperation:saveToDataBaseOperation];
[myQueue addOperation:downloadOperation];

编辑:为什么我__weak在操作中使用参考,可在此处找到。但总的来说是避免保留周期。


3
答案是正确的,但请注意正确的代码行是:NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];并且主线程上的NSOperationQueue无法挂起。
Gianluca P. 2014年

2
@ jacky-boy出于兴趣,为什么在最终代码片段中使用对downloadOperation和saveToDataBaseOperation的弱引用?
2014年

RuiAAPeres,嘿,我很好奇为什么最终代码段中使用了弱引用?您能对此有所说明吗?
2014年

@Pavan的想法是,如果您碰巧在其自己的块中引用了相同的块操作,则会有一个保留周期。供参考:conradstoll.com/blog/2013/1/19/…–
佩雷斯

那么哪个块与其中运行的块相同?
帕文2014年

16

如果要在后台线程中执行数据库写入操作,则需要NSManagedObjectContext为该线程创建一个。

您可以NSManagedObjectContext在相关NSOperation子类的start方法中创建背景。

在Apple文档中检查与核心数据的并发性。

您还可以创建一个NSManagedObjectContext在其自己的后台线程中NSPrivateQueueConcurrencyType执行请求的performBlock:方法,方法是使用创建该方法并在其方法内执行请求。


17
是什么让您认为这个问题与核心数据有关?
Nikolai Ruhe

11

NSOperationQueue

在iOS 4及更高版本中,操作队列使用Grand Central Dispatch执行操作。在iOS 4之前,它们为非并发操作创建单独的线程,并从当前线程启动并发操作。

所以,

[NSOperationQueue mainQueue] // added operations execute on main thread
[NSOperationQueue new] // post-iOS4, guaranteed to be not the main thread

在您的情况下,您可能想通过子类化来创建自己的“数据库线程”,NSThread并使用向其发送消息performSelector:onThread:


非常感谢...你救了我的命:[NSOperationQueue新] // iOS4后,保证不是主线程
ikanimo 2014年

9

NSOperation的执行线程取决于NSOperationQueue添加操作的位置。在您的代码中查找此语句-

[[NSOperationQueue mainQueue] addOperation:yourOperation]; // or any other similar add method of NSOperationQueue class

所有这一切都假定您没有做任何进一步的穿线main方法,NSOperation即您编写(预期)编写工作指令的实际方法是真正的怪物。

但是,在并发操作的情况下,情况有所不同。队列可以为每个并发操作生成一个线程。虽然它不是guarrantteed,它依赖于系统资源VS操作的资源需求,在这一点上在系统中。您可以通过其maxConcurrentOperationCount属性来控制操作队列的并发性。

编辑-

我发现您的问题很有趣,并且自己做了一些分析/记录。我在这样的主线程上创建了NSOperationQueue-

self.queueSendMessageOperation = [[[NSOperationQueue alloc] init] autorelease];

NSLog(@"Operation queue creation. current thread = %@ \n main thread = %@", [NSThread currentThread], [NSThread mainThread]);
self.queueSendMessageOperation.maxConcurrentOperationCount = 1; // restrict concurrency

然后,我继续创建NSOperation并使用addOperation将其添加。在此操作的主要方法中,当我检查当前线程时,

NSLog(@"Operation obj =  %@\n current thread = %@ \n main thread = %@", self, [NSThread currentThread], [NSThread mainThread]);

它不是主线程。并且,发现当前线程对象不是主线程对象。

因此,在主线程上自定义创建队列(其操作之间没有并发性)并不一定意味着这些操作将在主线程本身上串行执行。


我相信使用[[NSOperationQueue alloc] init]确保该队列将使用基础的默认后台全局DispatchQueue。因此,任何自定义的初始化OperationQueue都会将其任务送入后台DispatchQueues,从而将任务送入非主线程的线程中。要将操作馈入主线程,您必须使用来获取主OperationQueue [NSOperationQueue mainQueue]。这将获得在内部使用主DispatchQueue的主操作队列,因此最终将任务送入主线程。
Gordonium

1

来自文档的摘要是operations are always executed on a separate thread(iOS 4之后暗示GCD底层操作队列)。

检查它是否确实在非主线程上运行很简单:

NSLog(@"main thread? %@", [NSThread isMainThread] ? @"YES" : @"NO");

在线程中运行时,使用GCD / libdispatch在主线程上运行某些东西很简单,无论是核心数据,用户界面还是在主线程上运行所需的其他代码:

dispatch_async(dispatch_get_main_queue(), ^{
    // this is now running on the main thread
});

-2

如果要执行任何非平凡的线程处理,则应使用FMDatabaseQueue


1
该问题未提及特定数据库,例如fmdb或sqlite。
Nikolai Ruhe

没错,我根据上下文做出了一个假设。如果应用程序正在连接到多租户,线程安全的数据库(例如MySQL),那么这个问题就没有意义了。可以使用其他单用户数据库,但到目前为止,SQLite是最常见的数据库。
Holly
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.