2014-09-16 2 views
6

У меня есть одна большая задача, состоящая из нескольких подзадач. и я хочу добавить отчет о ходе выполнения этой большой задачи.
Для этого я хочу использовать NSProgress, и в соответствии с документацией по классу я могу выполнить этот вид подзадачности, используя его дочерний механизм.NSProgress странное поведение

Итак, чтобы упростить его, скажем, у меня есть большая задача, состоящая из одной подзадачи (конечно, в реальной жизни было бы больше подзадач). Так вот что я сделал:

#define kFractionCompletedKeyPath @"fractionCompleted" 

- (void)runBigTask { 
    _progress = [NSProgress progressWithTotalUnitCount:100]; // 100 is arbitrary 

    [_progress addObserver:self 
       forKeyPath:kFractionCompletedKeyPath 
        options:NSKeyValueObservingOptionNew 
        context:NULL]; 

    [_progress becomeCurrentWithPendingUnitCount:100]; 
    [self subTask]; 
    [_progress resignCurrent]; 
} 

- (void)subTask { 
    NSManagedObjectContext *parentContext = self.managedObjectContext; // self is AppDelegate in this example 
    NSManagedObjectContext *bgContext = [[NSManagedObjectContext alloc]initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 
    [bgContext setParentContext:parentContext]; 

    [bgContext performBlockAndWait:^{ 
     NSInteger totalUnit = 1000; 
     NSInteger completedUnits = 0; 
     NSProgress *subProgress = [NSProgress progressWithTotalUnitCount:totalUnit]; 

     for (int i=0; i < totalUnit; i++) { 

      // run some Core Data related code... 

      completedUnits++; 
      subProgress.completedUnitCount = completedUnits; 
     } 
    }]; 
}  

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 
    if ([keyPath isEqualToString:kFractionCompletedKeyPath]) { 
     if ([object isKindOfClass:[NSProgress class]]) { 
      NSProgress *progress = (NSProgress *)object; 
      NSLog(@"progress… %f", progress.fractionCompleted); 
     } 
    } else { 
     [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 
    } 
} 

Как вы можете видеть, подзадача использует фоновый контекст для выполнения какого-то кода, связанного Core Data, и фоновый контекст использует основной контекст как его родительский контекст.
Это вызывает некоторые странные KVO свойства «fractionCompleted» прогресса.

это печать:

progress… 1.000000 // why??? 
progress… 0.500000 // why????? 
progress… 1.000000 // why??????? 
progress… 0.666650 // why??????????? 
progress… 0.666990 
progress… 0.667320 
progress… 0.667660 
progress… 0.667990 
progress… 0.668320 
... 
progress… 1.000000 

Как вы можете видеть на печать начинается с 1,0, 0,5 и 1,0, а затем переходит на 0,66?!
отсюда он действует нормально и идет до 1.0, как я ожидаю.

Я попытался понять, почему это происходит, и я заметил, что если я удалю родительский контекст из фонового контекста, он отлично работает! Я получаю прогресс от 0.0 до 1.0.

Любые идеи, почему это происходит? и как я могу это исправить?

Я добавил оченьsimple project, чтобы продемонстрировать эту проблему (вы можете удалить setParentContext: позвонить, чтобы увидеть, что он хорошо работает и без него)

+0

Лучшая документация для NSProgress находится здесь: https://developer.apple.com/library/mac/releasenotes/Foundation/RN-Foundation/ – quellish

ответ

4

Трассировка стека, когда это происходит, выглядит следующим образом:

(lldb) bt 
* thread #1: tid = 0x81f2, 0x0000000105bffcda Foundation`-[NSProgress setTotalUnitCount:], queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 
    * frame #0: 0x0000000105bffcda Foundation`-[NSProgress setTotalUnitCount:] 
    frame #1: 0x0000000105bfeb1b Foundation`+[NSProgress progressWithTotalUnitCount:] + 87 
    frame #2: 0x0000000105a31213 Foundation`_NSReadBytesFromFileWithExtendedAttributes + 287 
    frame #3: 0x0000000105a3109d Foundation`-[NSData(NSData) initWithContentsOfFile:] + 89 
    frame #4: 0x0000000105a30b40 Foundation`+[NSDictionary(NSDictionary) newWithContentsOf:immutable:] + 101 
    frame #5: 0x0000000105a5622a Foundation`+[NSDictionary(NSDictionary) dictionaryWithContentsOfFile:] + 45 
    frame #6: 0x00000001043c4560 CoreData`-[NSManagedObjectModelBundle initWithPath:] + 224 
    frame #7: 0x00000001043c42ed CoreData`-[NSManagedObjectModel initWithContentsOfURL:] + 205 
    frame #8: 0x00000001040f723f CDProgress`-[AppDelegate managedObjectModel](self=0x00007fbe48c21f90, _cmd=0x000000010459b37b) + 223 at AppDelegate.m:127 
    frame #9: 0x00000001040f7384 CDProgress`-[AppDelegate persistentStoreCoordinator](self=0x00007fbe48c21f90, _cmd=0x000000010459c1cb) + 228 at AppDelegate.m:142 
    frame #10: 0x00000001040f708c CDProgress`-[AppDelegate managedObjectContext](self=0x00007fbe48c21f90, _cmd=0x0000000104598f0d) + 92 at AppDelegate.m:111 
    frame #11: 0x00000001040f6bdb CDProgress`-[AppDelegate subTask](self=0x00007fbe48c21f90, _cmd=0x00000001040f7997) + 43 at AppDelegate.m:45 
    frame #12: 0x00000001040f6b89 CDProgress`-[AppDelegate runTask](self=0x00007fbe48c21f90, _cmd=0x00000001040f7928) + 233 at AppDelegate.m:40 
    frame #13: 0x00000001040f6a4b CDProgress`-[AppDelegate application:didFinishLaunchingWithOptions:](self=0x00007fbe48c21f90, _cmd=0x0000000104f5dba9, application=0x00007fbe48f00fb0, launchOptions=0x0000000000000000) + 571 at AppDelegate.m:26 
    frame #14: 0x000000010477c5a5 UIKit`-[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 234 
    frame #15: 0x000000010477d0ec UIKit`-[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 2463 
    frame #16: 0x000000010477fe5c UIKit`-[UIApplication _runWithMainScene:transitionContext:completion:] + 1350 
    frame #17: 0x000000010477ed22 UIKit`-[UIApplication workspaceDidEndTransaction:] + 179 
    frame #18: 0x00000001088092a3 FrontBoardServices`__31-[FBSSerialQueue performAsync:]_block_invoke + 16 
    frame #19: 0x000000010615fabc CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12 
    frame #20: 0x0000000106155805 CoreFoundation`__CFRunLoopDoBlocks + 341 
    frame #21: 0x00000001061555c5 CoreFoundation`__CFRunLoopRun + 2389 
    frame #22: 0x0000000106154a06 CoreFoundation`CFRunLoopRunSpecific + 470 
    frame #23: 0x000000010477e799 UIKit`-[UIApplication _run] + 413 
    frame #24: 0x0000000104781550 UIKit`UIApplicationMain + 1282 
    frame #25: 0x00000001040f7793 CDProgress`main(argc=1, argv=0x00007fff5bb09308) + 115 at main.m:16 
    frame #26: 0x000000010686f145 libdyld.dylib`start + 1 
(lldb) 

То, что здесь происходит, что когда модель загружена, она читает файл Plist. Чтение файла plist вызывает -[NSData initWithContentsOfFile:], в котором ссылается +[NSProgress progressWithTotalUnitCount:] на основной поток. В качестве release notes point out это создаст NSProgress, который является дочерним элементом текущего прогресса. initWithContentsOfFile: на самом деле делает это, и создание нового ребенка из NSProgress вы создали:

<NSProgress: 0x7f9353596f80> : Parent: 0x0/Fraction completed: 0.0000/Completed: 0 of 1 
    <_NSProgressGroup: 0x7f935601a0d0> : Portion of parent: 100 Children: 1 
     <NSProgress: 0x7f935600bf50> : Parent: 0x7f9353596f80/Fraction completed: 0.0000/Completed: 0 of 0 

То, что здесь происходит то, что дополнительная работа добавляется перед вашими. На данный момент он ничего не знает о дополнительной работе, которую вы собираетесь добавить. Ребенок, добавленный initWithContentsOfFile:, завершается, сбрасывается с дерева, а затем вы начинаете добавлять свою работу.

Текущий прогресс начинается с 0 и переходит на 100%. Вы видите 100%, потому что ваши параметры KVO не включают NSKeyValueObservingOptionInitial.

NSData добавляет прогресс ребенка, который начинается с 0 и переходит на 100%.

Задача с базовыми данными добавляет дочерний элемент, который начинается с 0 и (в конечном итоге) переходит на 100%.

Ключевым моментом здесь является то, что вы используете performBlockAndWait:. Хотя сам блок работает в частной очереди, этот метод блокирует вызывающий поток, что задерживает ваши уведомления KVO. performBlockAndWait: также будет повторно использовать вызывающий поток, если это возможно, о чем нужно знать.

Если изменить способ subTask обернуть себя с NSProgress, чтобы служить в качестве родителя для всей единицы работы, в отставку ток в конце концов, вы, вероятно, получите поведение ближе к тому, что вы ожидаете:

- (void)subTask { 
    NSProgress *progress = [NSProgress progressWithTotalUnitCount:1]; 
    NSManagedObjectContext *parentContext = self.managedObjectContext; 
    NSManagedObjectContext *bgContext = [[NSManagedObjectContext alloc]initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 
    [bgContext setParentContext:parentContext]; 

    [progress becomeCurrentWithPendingUnitCount:1]; 
    [bgContext performBlock:^{ 

    ... stuff 

    [progress resignCurrent]; 
} 

NSProgress может быть немного сложнее обернуть вокруг вас, но с некоторым опытом становится легче. Обещаю!

+1

И, как побочный элемент, я бы поставил это как ошибку. В худшем случае они говорят вам, что это работает по назначению. – quellish

0

Оказывается, там должно быть счетчик NSProgress внутри [NSManagedObjectModel initWithContentsOfURL:]. Перед вводом [self subTask] вы настроитесь на получение уведомлений о любых индикаторах прогресса (установив _progress в качестве текущего и зарегистрировав себя, чтобы наблюдать изменения). Затем в этой рутине вы называете ленивого геттера self.managedObjectContext, который, в свою очередь, вызывает [NSManagedObjectModel initWithContentsOfURL:], у которого, по-видимому, есть счетчик прогресса 2 единицы. Кажется, вам нужно быть очень осторожным, когда вы размещаете вызовы на [NSProgress becomeCurrentWithPendingUnitCount:] и [NSProgress resignCurrent].

Смежные вопросы