Функция блока фоновой задачи не завершается - PullRequest
2 голосов
/ 10 января 2012

Я работаю над приложением для iphone, которое иногда запускает задачу в фоновом режиме, чтобы перегруппировать некоторые данные и загрузить их на сервер.Я использовал множество принципов из Grand Central Dispatch (GCD) с CoreData , чтобы начать работу, так как я редактирую объекты, которые сохраняются в Core Data, но код только иногда завершает работу, несмотря наприложение говорит, что у него осталось почти полных 600 секунд времени выполнения.

Код, который я использую:

__block UIBackgroundTaskIdentifier bgTask;
UIApplication *application = [UIApplication sharedApplication]; //Get the shared application instance

NSLog(@"BackgroundTimeRemaining before block: %f", application.backgroundTimeRemaining);

bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
    // Clean up any unfinished task business by marking where you.
    // stopped or ending the task outright.
    [application endBackgroundTask:bgTask];
    bgTask = UIBackgroundTaskInvalid;
}];

// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // Do the work associated with the task, preferably in chunks.

    NSLog(@"BackgroundTimeRemaining after block: %f", application.backgroundTimeRemaining);
    NSLog(@"Fixing item in the background");

    //Create secondary managed object context for new thread
    NSManagedObjectContext *backgroundContext = [[NSManagedObjectContext alloc] init];
    [backgroundContext setPersistentStoreCoordinator:[self.managedObjectContext persistentStoreCoordinator]];

    /* Save the background context and handle the save notification */
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(backgroundContextDidSave:)
                                                 name:NSManagedObjectContextDidSaveNotification
                                               object:backgroundContext];

    //creating runloop to kill location manager when done
    NSDate *stopDate = [[NSDate date] dateByAddingTimeInterval:60];
    [[NSRunLoop currentRunLoop] runUntilDate:stopDate];
    NSLog(@"Stop time = %@", stopDate);

    MasterViewController *masterViewContoller = [[MasterViewController alloc] init];
    masterViewContoller.managedObjectContext = backgroundContext;
    [[masterViewContoller locationManager] startUpdatingLocation];
    NSLog(@"Successfully fired up masterViewController class");

    [masterViewContoller adjustDataInBackground:FALSE];
    NSLog(@"Fixed Object!");

    //save background context
    [backgroundContext save:NULL];

    //unregister self for notifications
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:NSManagedObjectContextDidSaveNotification
                                                  object:backgroundContext];

    [application endBackgroundTask:bgTask];
    bgTask = UIBackgroundTaskInvalid;
});

Проблема в том, что «AdjustDataInBackground: FALSE» - довольно длинный методэто вызывает дополнительные вспомогательные методы (включая создание и сохранение основных объектов данных), и когда фоновая задача не позволяет завершить все эти методы, она повреждает мои данные.

Есть ли лучший способ справиться с этимвид операции?Мне нужно поместить весь мой необработанный код в блок фоновой задачи напрямую?

1 Ответ

3 голосов
/ 11 января 2012

Оказывается, у меня были две странные вещи, которые вызывали фоновую задачу:

  • Асинхронные URL-соединения (когда их метод инициализации завершился, iOS решила, что фоновая задача выполнена, даже еслиответ еще не получен)
  • Диспетчер местоположения, специфичный для фоновой задачи (очевидно, крупный нет-нет ... Apple имеет некоторую документацию по этому вопросу, но консоль иногда выдаёт ошибку об этом)

Вот код, который я сейчас использую (пока он работает):

    __block UIBackgroundTaskIdentifier bgTask;
UIApplication *application = [UIApplication sharedApplication]; //Get the shared application instance

NSLog(@"BackgroundTimeRemaining before block: %f", application.backgroundTimeRemaining);

bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
    // Clean up any unfinished task business by marking where you.
    // stopped or ending the task outright.
    [application endBackgroundTask:bgTask];
    bgTask = UIBackgroundTaskInvalid;
}];

// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // Do the work associated with the task, preferably in chunks.

    NSLog(@"BackgroundTimeRemaining after block: %f", application.backgroundTimeRemaining);

    //Create secondary managed object context for new thread
    NSManagedObjectContext *backgroundContext = [[NSManagedObjectContext alloc] init];
    [backgroundContext setPersistentStoreCoordinator:[self.managedObjectContext persistentStoreCoordinator]];

    /* Save the background context and handle the save notification */
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(backgroundContextDidSave:)
                                                 name:NSManagedObjectContextDidSaveNotification
                                               object:backgroundContext];

    //Set a grace period during which background updates can't stack up...
    //This number should be more than the longest combo of timeout values in adjustDataInBackground
    NSDate *stopDate = [[NSDate date] dateByAddingTimeInterval:90];
    __lastBackgroundSnapshot = stopDate;

    NSLog(@"Stop time = %@", stopDate);

    MasterViewController *masterViewContoller = [[MasterViewController alloc] init];
    masterViewContoller.managedObjectContext = backgroundContext;
    NSLog(@"Successfully fired up masterViewController class");

    [masterViewContoller adjustDataInBackground];
    NSLog(@"adjustDataInBackground!");

    //just in case
    [[self locationManager] stopUpdatingLocation];

    //save background context
    [backgroundContext save:NULL];

    NSLog(@"Uploading in background");
    //send results to server
    postToServer *uploadService = [[postToServer alloc] init];
    uploadService.managedObjectContext = backgroundContext;
    [uploadService uploadToServer];

    //save background context after objects are marked as uploaded
    [backgroundContext save:NULL];

    //unregister self for notifications
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:NSManagedObjectContextDidSaveNotification
                                                  object:backgroundContext];

    [application endBackgroundTask:bgTask];
    bgTask = UIBackgroundTaskInvalid;
});

Кроме того, я добавил следующий runloop к моим асинхронным объектам URLConnection, чтобы они осталисьживы достаточно долго, чтобы закончить свое дело.Хотя это не самый изящный способ его обработки, он работает до тех пор, пока вы можете корректно обработать сбой, если цикл выполнения завершается без завершения обмена сервером.

цикл выполнения (с учетом различных таймаутов в зависимости от задачи):

//marks the attempt as beginning
self.doneUpload = [NSNumber numberWithBool:FALSE];

[[uploadAttempt alloc] fireTheUploadMethod];

//if uploading in the background, initiate a runloop to keep this object alive until it times out or finishes
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground)
{
    //Timeout length to wait in seconds to allow for async background execution
    NSDate *stopDate = [[NSDate date] dateByAddingTimeInterval:120];

    do {
        NSLog(@"Waiting for upload to return, time left before timeout: %f", [stopDate timeIntervalSinceNow]);
        [[NSRunLoop currentRunLoop] runUntilDate:stopDate];
    } while ([stopDate timeIntervalSinceNow] > 0 && self.doneUpload == [NSNumber numberWithBool:FALSE]);
}

Надеюсь, это поможет любому, кто столкнется с этим в будущем!

...