Асинхронный какао — предотвращение простых (очевидных) взаимоблокировок в NSOperation?

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

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

@interface ToyOperation : NSOperation

@end

@implementation ToyOperation

- (void)main
{
    // Lots of work

    NSString *string = @"Important Message";
    [self performSelector:@selector(sendMainThreadSensitiveMessage:) onThread:[NSThread mainThread] withObject:string waitUntilDone:YES];

    // Lots more work
}

- (void)sendMainThreadSensitiveMessage:(NSString *)string
{
    // Update the UI or something that requires the main thread...
}

@end

- (int)main
{
    ToyOperation *op = [[ToyOperation alloc] init];
    NSOperationQueue *opQ = [[NSOperationQueue alloc] init];
    [opQ addOperations: @[ op ] waitUntilFinished:YES];    // Deadlock

    return;
}

person edelaney05    schedule 29.06.2013    source источник


Ответы (1)


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

Есть. Никогда не выполняйте синхронный вызов основной очереди. И дополнение: Никогда не выполняйте синхронный вызов из основной очереди. И, на самом деле, это можно резюмировать так: Никогда не выполняйте синхронный вызов из любой очереди в любую другую очередь.< /эм>

Делая это, вы гарантируете, что основная очередь не заблокирована. Конечно, может быть исключительный случай, который соблазняет вас нарушить это правило, и даже случаи, когда это действительно неизбежно. Но это должно быть исключением, потому что даже одна функция dispatch_sync() (или NSOpQueue waitUntilDone) может зайти в тупик.

Конечно, обновление данных из очереди в очередь может быть непростой задачей. Есть несколько вариантов; уровень данных, безопасный для параллелизма (очень сложный), передача только неизменяемых объектов или копий данных (обычно в основную очередь для целей отображения - довольно просто, но потенциально дорого), или вы можете перейти к отказоустойчивости на основе UUID, такой как модель, которая Базовые данные использует. Независимо от того, как вы решите эту проблему, в ней нет ничего нового по сравнению с любой другой моделью параллелизма.

Единственным исключением является замена блокировок очередями (например, вместо использования @synchronized() внутри класса используйте последовательную очередь GCD и используйте dispatch_sync() в этой очереди везде, где должна выполняться синхронизированная операция. Быстрее и проще .). Но это не столько исключение, сколько решение совсем другой задачи.

person bbum    schedule 29.06.2013