【问题标题】:NSOperation and NSOperationQueue working thread vs main threadNSOperation 和 NSOperationQueue 工作线程与主线程
【发布时间】:2013-11-03 08:32:32
【问题描述】:

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

这是应用场景:

  • 获取某个地点的所有邮政编码。
  • 为每个邮政编码获取所有房屋。
  • 为每所房屋获取居民详细信息

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

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

【问题讨论】:

  • 如果将操作添加到主队列,那么它们将在主线程中执行。如果您创建自己的 NSOperationQueue 并向其添加操作,那么它们将在此队列的线程中执行。
  • 我不认为你会得到比@Cy-4AH 给出的更好的答案,除非你得到更具体/发布一些代码。我会说你总是可以在代码中放置一个断点,当它跳闸时,它会告诉你跟踪所在的线程。
  • “NSOperation 中的委托将接收数据”是什么意思。意思是? NSOperationNSOperationQueue 都不包含委托属性。
  • 你也可以将你的委托调用推送到主线程,而不是对当前线程做任何假设......

标签: ios objective-c multithreading nsoperation nsoperationqueue


【解决方案1】:

如果你正在做任何重要的线程,你应该使用FMDatabaseQueue

【讨论】:

  • 问题没有提到具体的数据库,比如 fmdb 或 sqlite。
  • 没错,我根据上下文做了一个假设。如果应用程序连接到多租户、线程安全的数据库,例如 MySQL,那么这个问题就没有意义了。还有其他可以使用的单用户数据库,但 SQLite 是迄今为止最常见的。
【解决方案2】:

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

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

所有这一切都假设您没有在NSOperationmain 方法中进行任何进一步的线程化,这是您(预期)编写工作说明的实际怪物。

但是,在并发操作的情况下,情况就不同了。队列可以为每个并发操作生成一个线程。虽然它不是保证的,它取决于系统资源与系统中的操作资源需求当时。可以通过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 添加它。当我检查当前线程时,在这个操作的 main 方法中,

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

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

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

【讨论】:

  • 我相信使用[[NSOperationQueue alloc] init] 可以确保队列将使用底层默认后台全局 DispatchQueue。因此,任何自定义初始化的 OperationQueue 都会将其任务提供给后台 DispatchQueues,从而将任务提供给不是主线程的线程。要将操作提供给主线程,您必须使用类似[NSOperationQueue mainQueue] 的方式获取主 OperationQueue。这获得了在内部使用主 DispatchQueue 的主操作队列,因此最终将任务提供给主线程。
【解决方案3】:

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

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

查看 Apple 文档以获取 Concurrency with Core Data.

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

【讨论】:

  • 是什么让您认为这个问题与 Core Data 相关?
【解决方案4】:

来自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 new] //iOS4后,保证不是主线程
【解决方案5】:

文档的摘要是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
});

【讨论】:

    【解决方案6】:

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

    如果您像这样从头开始创建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 可以更好地控制您想做的事情。您可以在两个操作之间创建依赖关系(下载并保存到数据库)。例如,要在一个块和另一个块之间传递数据,您可以假设 NSData 将来自服务器,因此:

    __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 参考进行操作,可以找到here。但简而言之就是避免保留循环。

    【讨论】:

    • 答案是正确的,但是请注意正确的代码行是:NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];,并且主线程上的NSOperationQueue不能被挂起。
    • @jacky-boy 出于兴趣,为什么在最终代码 sn-p 中使用了对 downloadOperation 和 saveToDataBaseOperation 的弱引用?
    • RuiAAPeres,嘿,我很好奇为什么最终代码 sn-p 中使用了弱引用?你能解释一下吗?
    • @Pavan 的想法是,如果您碰巧在其自己的块中引用了相同的块操作,您就会得到一个保留周期。供参考:conradstoll.com/blog/2013/1/19/…
    • 那么哪个块和它里面运行的块是一样的?
    猜你喜欢
    • 1970-01-01
    • 2021-01-31
    • 1970-01-01
    • 1970-01-01
    • 2017-07-30
    • 2013-10-09
    • 1970-01-01
    • 2016-12-19
    • 1970-01-01
    相关资源
    最近更新 更多