NSOperationQueue在后台运行时无法等待所有操作完成

5
我正在开发的应用程序会定期从应用程序服务器刷新其本地数据缓存(10多个请求,每个请求都需要相当长时间)。为了不阻塞UI线程,我目前将这些请求异步运行。由于这些请求确实需要一段时间来处理并加载到核心数据中,因此我想利用beginBackgroundTaskWithExpirationHandlerNSOperationQueue的依赖操作行为。
在将所有请求添加到操作队列之后,我使用waitUntilAllOperationsAreFinished来阻塞直到所有操作完成(这不在主线程上)。我在原型中看到的问题是,当我运行应用程序并立即将其转到后台(按下home按钮)时,waitUntilAllOperationsAreFinished仍然被阻塞,即使所有操作都已经完成...但是只要我再次打开应用程序,处理程序就会完成。如果我让应用程序保持在前台,一切都会很顺利。这种行为并不总是发生在我的实际应用程序中,但在下面的示例代码中似乎发生了:
#import "ViewController.h"

@interface ViewController ()

@property (assign, nonatomic) UIBackgroundTaskIdentifier task;
@property (strong, nonatomic) NSOperationQueue *queue;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    [self performSelectorInBackground:@selector(queueItUp) withObject:nil];
}

- (void)queueItUp {
    UIApplication *application = [UIApplication sharedApplication];

    self.queue = [[NSOperationQueue alloc] init];
    self.task = [application beginBackgroundTaskWithExpirationHandler:^{
        NSLog(@"Took too long!");

        [self.queue cancelAllOperations];
        [application endBackgroundTask:self.task];
        self.task = UIBackgroundTaskInvalid;
    }];

    for (int i = 0; i < 5; i++) {
        [self.queue addOperationWithBlock:^{
            [NSThread sleepForTimeInterval:3];
            NSLog(@"Finished operation.");
        }];
    }


    NSLog(@"Waiting until all operations are finished.");

    [self.queue waitUntilAllOperationsAreFinished];

    [application endBackgroundTask:self.task];
    self.task = UIBackgroundTaskInvalid;

    NSLog(@"All done :)");
}

@end

我做错了什么?

谢谢

2个回答

4
您的示例代码在iOS 6.1上的iPhone或模拟器上没有任何永久阻塞的地方。在[application endBackgroundTask:self.task];这一行设置断点,您每次都会命中它。
您看到的行为是因为您告诉应用程序结束后台任务后仍然在后台记录日志。我不确定具体细节,但日志以某种方式排队,以便在应用程序恢复到前台时打印到控制台。可能情况是线程执行被暂停。
如果您将NSLog(@"All done");移动到调用endBackgroundTask:之前,您将看到记录的输出。

1
使用您的示例代码,操作完成得非常好。问题出在NSLog(@"All done :)");,您被挂起了。您的队列仍然存在且没有待处理的操作,但是您可能不再具有活动运行循环,并且主线程被阻塞,因为您正在后台中。由于您已经完成了待处理的后台操作,因此您被阻塞。当您恢复时,应用程序将从中断的地方继续执行。如果您这样做:
    [[self queue] addOperationWithBlock:^{
        NSLog(@"All done :)");
    }];

行为应该更加明显。它正在按照你的指示执行。
你有一堆操作排队等待执行,并且已经调用了waitUntilAllOperationsAreFinished。当它们完成后,你会在后台被阻塞。
看起来你想在这些操作完成后执行某些操作。NSOperation提供了依赖关系和完成块的功能,允许你构建这种行为。你可以分组操作并设置一个完成块或操作,以在你的操作组完成时运行。其中一些内容在并发编程指南中有所涉及。

嗯...我想我原本计划的是使用waitUntilAllOperationsAreFinished作为一种屏障/门,一旦通过该门,执行完成操作(在这种情况下,将所有下载的数据加载到Core Data中)。您是建议我创建另一个操作,该操作依赖于所有其他操作,以执行该完成操作吗?从而避免甚至使用waitUntilAllOperationsAreFinished的需要? - chinabuffet
你可以这样做,你可以执行一个包含所有其他操作依赖的单个操作,你可以执行一个包含其他操作的操作等。或者如果操作不是并发的,可以将一个操作安排为队列中执行完成操作的最后一项。你仍然需要使用“waitUntilAllOperationsAreFinished”来确保队列在后台运行时。你只需要在调用它之前将你的完成操作 - 无论是什么 - 放入队列中即可。 - quellish
如果后台任务已经启动,为什么在这种情况下仍需要使用waitUntilAllOperationsAreFinished - chinabuffet
您的队列在所有排队操作完成之前仍然可以被暂停。 - quellish

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接