Block_release освобождает объекты пользовательского интерфейса в фоновом потоке

Один из шаблонов, представленных на конференции WWDC 2010 «Blocks and Grand Central Dispatch», заключался в использовании вложенных вызовов dispatch_async для выполнения трудоемких задач в фоновом потоке с последующим обновлением пользовательского интерфейса в основном потоке после завершения задачи.

dispatch_async(backgroundQueue, ^{
    // do something time consuming in background
    NSArray *results = ComputeBigKnarlyThingThatWouldBlockForAWhile();

    // use results on the main thread
    dispatch_async(dispatch_get_main_queue(), ^{
        [myViewController UpdateUiWithResults:results];
    });
});

Поскольку «myViewController» используется внутри блоков, он автоматически получает «сохранить» и позже получит «освобождение», когда блоки будут очищены.

Если вызов «release» блока является последним вызовом выпуска (например, пользователь уходит из представления во время выполнения фоновой задачи), вызывается метод dealloc myViewController, но он вызывается в фоновом потоке !!

Объекты UIKit не любят, когда их выделяют вне основного потока. В моем случае UIWebView выдает исключение.

Как может этот шаблон, представленный WWDC, специально упомянутый как лучший новый способ избежать блокировки пользовательского интерфейса, быть настолько ошибочным? Я что-то упускаю?


person Ralph Marczynski    schedule 15.06.2011    source источник
comment
Какого рода исключение вы получаете?   -  person Deepak Danduprolu    schedule 15.06.2011
comment
У меня его нет, но, перефразируя, исключение говорит что-то вроде невозможности получить блокировку в основном потоке или потоке веб-просмотра ...   -  person Ralph Marczynski    schedule 17.06.2011
comment
У меня такая же проблема. Ошибка: bool _WebTryThreadLock (bool), 0x2eb710: попытка получить веб-блокировку из потока, отличного от основного потока или веб-потока. Это может быть результатом вызова UIKit из вторичного потока. Теперь сбой ... Я попытался выполнить сохранение, а затем (в dispatch_async) выполнить выпуск через performSelectorOnMainThread, но dispatch_async выполнил собственное сохранение и выпуск, поэтому последний выпуск ВСЕ ЕЩЕ происходит в фоновом потоке.   -  person Jeff    schedule 26.06.2011
comment
Лучшее решение, которое мне удалось придумать, - это проверить поток в методе dealloc ViewController. Если поток является фоновым потоком, отправьте [self dealloc] в основной поток.   -  person Ralph Marczynski    schedule 11.08.2011
comment
Разве внутренний блок не должен быть последним, который большую часть времени освобождает контроллер представления? И это происходит в основном потоке. Так в чем проблема? Внешний блок планирует внутренний блок в основном потоке. Затем внешний блок завершается и освобождается, что освобождает контроллер представления. Внутренний блок должен по-прежнему сохранять контроллер представления, если (что очень маловероятно) он работает очень быстро и завершается до завершения внешнего блока. Поэтому, когда внутренний блок завершен, он освобождается в основном потоке и освобождает контроллер представления в основном потоке. Верно?   -  person user102008    schedule 08.08.2012
comment
@ user102008 в большинстве случаев недостаточно - это иногда приводит к освобождению фона, причем чаще, чем вы думаете. Я создал модульный тест, чтобы решить эту проблему, и результаты были довольно неожиданными.   -  person Airsource Ltd    schedule 10.07.2014


Ответы (3)


В таком случае можно использовать квалификатор типа хранилища __block. __block переменные не сохраняются автоматически блоком. Итак, вам нужно сохранить объект самостоятельно:

__block UIViewController *viewController = [myViewController retain];
dispatch_async(backgroundQueue, ^{
    // Do long-running work here.
    dispatch_async(dispatch_get_main_queue(), ^{
        [viewController updateUIWithResults:results];
        [viewController release]; // Ensure it's released on main thread
    }
});

ИЗМЕНИТЬ

С ARC объект переменной __block автоматически сохраняется в блоке, но мы можем установить значение nil для переменной __block для освобождения сохраненного объекта, когда захотим.

__block UIViewController *viewController = myViewController;
dispatch_async(backgroundQueue, ^{
    // Do long-running work here.
    dispatch_async(dispatch_get_main_queue(), ^{
        [viewController updateUIWithResults:results];
        viewController = nil; // Ensure it's released on main thread
    }
});
person Kazuki Sakamoto    schedule 30.06.2011
comment
вы также можете использовать __weak, чтобы избежать его сохранения в блоке под ARC - person Catfish_Man; 31.10.2012
comment
Использование __weak снизит вероятность фонового освобождения, но не предотвратит его полностью. - person Airsource Ltd; 10.07.2014

В потоке я просто использую [viewController retain];, а в конце потока использую [viewController release]. Это работает, и я не использую GCD~

person winlin    schedule 30.10.2012

Это сработало для меня (добавил таймер):

[self retain]; // this guarantees that the last release will be on the main threaad
dispatch_async(backgroundQueue, ^{
    // do something time consuming in background
    NSArray *results = ComputeBigKnarlyThingThatWouldBlockForAWhile();

    // use results on the main thread
    dispatch_async(dispatch_get_main_queue(), ^{
        [myViewController UpdateUiWithResults:results];
        [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(releaseMe:) userInfo:nil repeats:NO];
    });
});
- (void)releaseMe:(NSTimer *)theTimer {
    [self release]; // will be on the main thread
}
person Jeff    schedule 26.06.2011
comment
Это не потокобезопасно. Вы не можете с уверенностью предположить, что блок будет освобожден в течение 0,1 секунды (и, следовательно, последний вызов освобождения находится в основном потоке). Конечно, вероятность исчезающе мала; но он по-прежнему не равен нулю, и этот код может дать сбой. - person Adam Ernst; 18.08.2011
comment
Ах, релиз будет в фоновом потоке, если блок не был освобожден. Как бы вы принудительно разместили релиз в основном потоке? - person Jeff; 16.10.2011