2013-08-21 3 views
1

Я использую MagicalRecord, и в одном из контроллеров моего представления источник данных питается данными, поступающими в базу данных. Поскольку процесс подачи включает несколько этапов, я попытался использовать GCD для ускорения всего процесса. Общий процесс выглядит так.MagicalRecord и dispatch_group

Работы, которые необходимо выполнить после получения доступа к свойствам в globalSummary. Проблема в том, что приложение зависает в dispatch_group_wait. Я попытался добавить работу к шагу, и он работает в начале, но поскольку я добавляю больше работы, он работает, если я иду шаг за шагом через код, но не работает, если я позволю ему запустить.

Что-то не так с подходом или оно конфликтует с MagicalRecord в некотором роде?

- (NSArray *)dataSource 
{ 
    if (_dataSource == nil) { 

    _dataSource = [NSMutableArray array]; 
    NSManagedObjectContext *privateContext = [NSManagedObjectContext contextWithStoreCoordinator:[NSPersistentStoreCoordinator defaultStoreCoordinator]]; 

    GlobalSummary *globalSummary = [GlobalSummary insertInManagedObjectContext:privateContext]; // holds a bunch of fetched properties 

    dispatch_queue_t queue = dispatch_queue_create("de.berndrabe.dataSource", DISPATCH_QUEUE_CONCURRENT); 
    dispatch_group_t outerGroup = dispatch_group_create(); 

    __block SectionInfo *siPilotAircraft = nil; 
    if ([PilotAircraft countOfEntities]) { 
     dispatch_group_async(outerGroup, queue, ^{ 
      NSArray *frPilotAircraft = [PilotAircraft findAll]; 
      // do some processing an set SectionInfo variable 
     }); 
    } 

    __block SectionInfo *siMedicals = nil; 

    if ([PilotMedical countOfEntities]) { 
     dispatch_group_async(outerGroup, queue, ^{ 
      NSArray *frPilotMedical = [PilotMedical findAll]; 
     }); 
    } 

    // more working packets following the same patter 

    dispatch_group_wait(outerGroup, DISPATCH_TIME_FOREVER); 

    if (siPilotAircraft.countOfRows) { 
     [_dataSource addObject:siPilotAircraft]; 
    } 
    if (siMedicals.countOfRows) { 
     [_dataSource addObject:siMedicals]; 
    } 
} 

return _dataSource; 
} 

EDIT: Использование [Entity findAllInContext:privateContext] позволяет извлечет запись мне нужна, но она все еще теперь я застряла, когда доступ к неисправным отношениям в одном субъекте :(

ответ

0

Я нашел решение моей проблемы. Здесь снова (короче), что я пытался сделать.

Задача построить NSMutableArray, которое будет выступать в качестве объекта DataSource для UITableView каждая запись включает в себя выборку основных данных и обработки операции parallelise работы и добавить результаты по завершению, убедитесь, что доступ к NSMutableArray регулируются начать работать с объектом DataSource по завершению

Проблемы количество записей в объекте DataSource может варьироваться

Здесь был работали меня. Категория «NSMutableArray» - это удобный метод, обеспечивающий сериализацию доступа. С этой реализацией вы максимально распараллеливали работу, и вы можете savely написать письмо

[self prepareDataSource]; [self.tableView reloadData];

, не опасаясь, что какая-то работа еще продолжается.

dispatch_queue_t queue = dispatch_queue_create("com.yourdomain.serializedAccess", DISPATCH_QUEUE_CONCURRENT); 

- (void)prepareDataSource 
{ 
    [self.dataSource removeAllObjects]; 

    dispatch_group_t group = dispatch_group_create(); 

    [self prepareWorkEntry1FromManagedObjectContext:self.privateContext forDataSource:self.dataSource group:group]; 
    [self prepareWorkEntry2FromManagedObjectContext:self.privateContext forDataSource:self.dataSource group:group]; 
    [self prepareWorkEntry3FromManagedObjectContext:self.privateContext forDataSource:self.dataSource group:group]; 

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER); 
} 

- (void)prepareWorkEntry1FromManagedObjectContext:(NSManagedObjectContext *)context forDataSource:(NSMutableArray *)array group:(dispatch_group_t)group 
{ 
    __weak typeof(self) weakSelf = self; 

    dispatch_group_enter(group); 
    [context performBlock:^{ 
    Object *object = nil; 
     createObject(object); 

    if (object) { 
      [array serializedAddObject:object withQueue:weakSelf.queue group:group]; 
     } 
     else { 
      dispatch_group_leave(group); 
     } 
    }]; 
} 


@implementation NSMutableArray (ResourceProtection) 

- (void)serializedAddObject:(id)object withQueue:(dispatch_queue_t)queue group:(dispatch_group_t)group 
{ 
    if (group == NULL) { 
     dispatch_barrier_async(queue, ^{ 
      [self addObject:object]; 
     }); 
    } else { 
     dispatch_barrier_async(queue, ^{ 
      [self addObject:object]; 
      dispatch_group_leave(group); 
     }); 
    } 
} 

@end 

EDIT: Что касается категории ResourceProtection обеспокоен вы должны убедиться, что

  1. dispatch_group_enter вызывается перед dispatch_group_leave
  2. dispatch_group_enter и dispatch_group_leave сбалансированы

Вот это является наблюдаемой, синхронизированной схемой записи для изменяемого объекта.

+0

Можете ли вы фактически подтвердить, что 'performBlock:' будет выполнять данные блоки параллельно по отношению к другим блокам? Боюсь, они запускаются сериализованными. Кроме того, небольшое улучшение было бы сделать 'prepareDataSource:' асинхронным, имеющий блок завершения, который вызывается, когда все асинхронные задачи (executeBlock :) завершены. dispatch_group_wait без необходимости приводит к блокировке вызывающего потока до тех пор, пока все задачи не будут завершены. – CouchDeveloper

+0

Как утверждает Apple в своей документации, это асинхронный вызов, поэтому он немедленно возвращается. Тем не менее он помещает блок в свою приватную очередь и (я думаю) сделает все возможное, чтобы сделать работу максимально быстрой. Но поскольку это может включать в себя доступ к базе данных, он может быть сериализован. В последнем пункте, о котором вы говорили (group_wait ...), этого я хотел достичь. Мне нужно было полностью настроить объект dataSource, прежде чем я начну использовать его. –

0

Я хотел бы предложить другой подход, который использует вспомогательную библиотеку, которая значительно упрощает реализацию асинхронных задач: она использует «Обещания» в качестве средства оповещения о завершении (или сбое) асинхронных задач.

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

- (RXPromise*) doSomethingAsync; 

конечный результат соответственно сбой может быть получено с помощью обработчиков, которые будут «зарегистрированными "как показано ниже:

- (void) foo 
{ 
    RXPromise* promise = [self doSomethingAsync]; 
    promise.then(<completion handler block>, <error handler block>); 
} 

или короче, и с обработчиком заполнителей, заполненных фактическим кодом:

- (void) foo 
{ 
    [self doSomethingAsync] 
    .then(^id(id result) 
    { 
     // result is the eventual result of the asynchronous task 
     return nil; // return the result of the handler 

    }, id(NSError*error) 
    { 
     NSLog(@"ERROR: %@, error"); // the task failed with error 
     return nil; 
    }); 
} 

Примечание: сами обработчики выполняются в закрытом потоке (фактическая параллельная очередь отправки). Для синхронизации совместного доступа внутри обработчиков можно явно указать очереди отправки, где будет выполняться обработчик:

- (void) foo 
{ 
    [self doSomethingAsync] 
    .thenOn(dispatch_get_main_queue()), ^id(id result) 
    { 
     // Here, we are executing on the main thread 
     // result is the eventual result of the asynchronous task 
     return nil; // return the result of the handler 

    }, id(NSError*error) 
    { 
     // Here, we are executing on the main thread 
     NSLog(@"ERROR: %@, error"); // the task failed with error 
     return nil; 
    }); 
} 

Обратите внимание, что асинхронные задачи, выполняемые в [self doSomethingAsync] могут быть выполнены на его собственные нитях (или очереди) ,

«Продолжение» более одного асинхронных задач может быть легко достигнуто:

task1() 
.then(^id(id result1{ 
    return task2(result1); 
}, nil) 
.then(^id(id result2) { 
    return task3(result2); 
}, nil) 
.then(nil, ^id(NSError*error) 
    NSLog(@"Something went wrong in task1 or task2 or task3: %@", error); 
    return nil; 
); 

После этого краткого введения вы можете решить проблему следующим образом:

Определите асинхронные задачи, которые возвращают RXPromise:
- (RXPromise*) prepareWorkEntry1FromManagedObjectContext:(NSManagedObjectContext *)context; 
- (RXPromise*) prepareWorkEntry2FromManagedObjectContext:(NSManagedObjectContext *)context; 
- (RXPromise*) prepareWorkEntry3FromManagedObjectContext:(NSManagedObjectContext *)context; 


- (RXPromise*) prepareWorkEntry1FromManagedObjectContext:(NSManagedObjectContext *)context 
{ 
    RXPromise* promise = [RXPromise new]; 
    [context performBlock:^{ 
     Object *object = nil; 
     createObject(object); 

     if (object) { 
      // success 
      [promise fulfillWithValue:object]; 
     } 
     else { 
      // failure 
      [promise rejectWithReason:@"object creation failed"]; 
     } 
    }]; 

    return promise; 
} 

Аналогичным образом выполняйте другие задачи.

Объединить результаты в обработчиках:
- (void) foo 
{ 
    [self prepareWorkEntry1FromManagedObjectContext:moc] 
    .thenOn(self.sync_queue, (^id(id object)) { 
     [self.dataSourceArray addObject:object]; 
     return @"Finished 1"; 
    }, nil); 

    [self prepareWorkEntry2FromManagedObjectContext:moc] 
    .thenOn(self.sync_queue, (^id(id object)) { 
     [self.dataSourceArray addObject:object]; 
     return @"Finished 2"; 
    }, nil); 

    [self prepareWorkEntry3FromManagedObjectContext:moc] 
    .thenOn(self.sync_queue, (^id(id object)) { 
     [self.dataSourceArray addObject:object]; 
     return @"Finished 3"; 
    }, nil); 

} 

Обратите внимание, что dataSourceArray будет доступен в специальном sync_queue. Это будет очередная диспетчерская очередь.

Обратите внимание, что этот метод foo эффективно асинхронен.

Существует также возможность «асинхронно ждать», пока количество асинхронных задач не было завершено:

- (void) foo { 

    NSArray* promises = @[ 

    [self prepareWorkEntry1FromManagedObjectContext:moc] 
    .thenOn(self.sync_queue, (^id(id object)) { 
     [self.dataSourceArray addObject:object]; 
     return @"Finished 1"; 
    }, nil), 

    [self prepareWorkEntry2FromManagedObjectContext:moc] 
    .thenOn(self.sync_queue, (^id(id object)) { 
     [self.dataSourceArray addObject:object]; 
     return @"Finished 2"; 
    }, nil), 

    [self prepareWorkEntry3FromManagedObjectContext:moc] 
    .thenOn(self.sync_queue, (^id(id object)) { 
     [self.dataSourceArray addObject:object]; 
     return @"Finished 3"; 
    }, nil) 

    ]; 

    [RXPromise all:promises] 
    .then(^id(id arrayOfPromises){ 
     // all tasks *and its handlers* finished. 
     ... 
     return nil; 
    }, nil); 
} 

Я автор этой библиотеки, которые вы можете найти здесь: RXPromise.

+0

В вашем - (RXPromise *) готовят ..реализация executeBlock - это асинхронный вызов, поэтому он немедленно возвратит вновь созданный объект RXPromise, не достигнув при этом методов fulFill или reject. –

+0

Да, само обещание будет немедленно возвращено, но результат будет доступен только после того, как обещание будет выполнено или отклонено. Это происходит в блоке, который выполняется в произвольные моменты времени после его передачи в контекст. Используя обещания, код выглядит так, как если бы он был синхронным. ;) – CouchDeveloper

+0

Может быть, короткое описание того, что здесь происходит, может быть полезно: когда вы вызываете 'foo', вы ставите в очередь три блока -« рабочую нагрузку »для контекста. 'foo' немедленно возвращается - это асинхронно. В конечном итоге каждая рабочая нагрузка закончится, и будет вызван обработчик соответствующего обещания, который выполняется на sync_queue. Каждый обработчик добавит результат задачи в 'dataSourceArray'. Рабочие нагрузки могут выполняться параллельно, но, вероятно, они будут выполняться последовательно контекстом. Это не относится к обработчикам, они сериализуются в sync_queue. – CouchDeveloper