NSOperationQueue waittillalloperationsarefinished не работает в фоновом режиме


Приложение, над которым я работаю, периодически обновляет локальный кэш данных с сервера приложений (10+ запросов, каждый из которых занимает довольно много времени). В настоящее время я выполняю эти запросы асинхронно, чтобы не блокировать поток пользовательского интерфейса. Поскольку эти запросы требуют некоторого времени для обработки и последующей загрузки в основные данные, я хотел бы использовать beginBackgroundTaskWithExpirationHandler и зависимое поведение операций NSOperationQueue.

После того, как я добавил все свои запросы в очередь операций, я использую 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 5

2 ответа:

Ваш пример кода не блокируется постоянно в любой точке iOS 6.1 на iPhone или симуляторе. Поставьте точку останова на линии [application endBackgroundTask:self.task];, и вы будете попадать в нее каждый раз.

Поведение, которое вы видите, связано с тем, что вы регистрируете в фоновом режиме после того, как вы сказали приложению завершить фоновую задачу. Я не уверен в точных деталях, но журналы каким-то образом помещаются в очередь для печати на консоль после восстановления вашего приложения на передний план. Возможно, так оно и есть. вместо этого выполнение потока приостанавливается.

Если вы переместите ваш NSLog(@"All done"); до перед вызовом в endBackgroundTask:, вы увидите протоколированный вывод.

Используя Ваш пример кода, операции завершаются просто отлично. Это на NSLog(@"All done :)");, что вы висите. Ваша очередь все еще существует и не имеет ожидающих операций, но у вас больше не может быть активного runloop, и основной поток блокируется как вы, так и в фоновом режиме. Поскольку вы завершили ожидающие фоновые операции, вы блокируетесь. Когда вы возобновляете свое приложение, оно продолжает с того места, на котором остановилось. Если вы сделаете это:

    [[self queue] addOperationWithBlock:^{
        NSLog(@"All done :)");
    }];
Поведение должно быть еще более очевидным. Оно делает именно то, что вы говорите ему делать.

У вас есть куча операций в очереди, и вы вызвали waitUntilAllOperationsAreFinished. Когда они заканчивают, вы блокируетесь в фоновом режиме.

Похоже, что вы пытаетесь выполнить что-то, когда эта группа операций закончена. NSOperation предоставляет возможность иметь зависимости и блоки завершения, которые позволяют построить такой тип поведения. Вы можете сгруппировать операции и установить блок завершения или операцию, которая выполняется, когда ваша группа операции завершены. Некоторые из них описаны в руководстве по программированию параллелизма