Серийная очередь GCD, кажется, не выполняется последовательно

У меня есть метод, который иногда может быть вызван во всем моем коде. Ниже приведен очень простой пример, так как код обрабатывает изображения и файлы из фотогалереи iphone и помечает их как уже обработанные при использовании метода.

@property (nonatomic, assign) dispatch_queue_t serialQueue;

....

-(void)processImages
{
    dispatch_async(self.serialQueue, ^{
        //block to process images
        NSLog(@"In processImages");

        ....

        NSLog(@"Done with processImages");
    });
}

Я думаю, что каждый раз, когда вызывается этот метод, я получаю следующий вывод: "В processImages", "Выполнено с processImages", "В ProcessImages", "Выполнено с processImages" и т. Д...

но я всегда получаю

"В процессе изображения" "В процессе изображения" "Сделано с процессом изображения" "Сделано с процессом изображения" и т. Д...

Я думал, что последовательная очередь будет ждать до завершения первого блока, а затем начать. Мне кажется, что он запускает метод, затем он вызывается снова и запускается до того, как первый вызов даже завершается, создавая дубликаты изображений, которые обычно не будут обрабатываться из-за того факта, что, если он действительно выполняется последовательно, метод будет знать, что они были уже обработаны. Может быть, мое понимание последовательных очередей не является конкретным. Любой вклад? Спасибо.

РЕДАКТИРОВАТЬ: БОЛЬШЕ Контекст ниже, это то, что происходит в блоке... Может ли это вызвать проблему???

@property (nonatomic, assign) dispatch_queue_t serialQueue;

....

-(void)processImages
{
    dispatch_async(self.serialQueue, ^{
        //library is a reference to ALAssetsLibrary object 

        [library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop)
        {
            [group enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop)
            {
             ....
             //Process the photos here
            }];
        failureBlock:^(NSError *error) { NSLog(@"Error loading images from library");
        }];

    });
}

-(id)init
{
    self = [super init];
    if(self)
    {
        _serialQueue = dispatch_queue_create("com.image.queue",NULL);
    }
    return self;
}

этот объект создается только один раз, и, насколько я могу судить, он никогда не может быть создан снова на основе моего кода... Хотя я проверю тесты, чтобы убедиться в этом.

ОБНОВЛЕНИЕ 2: Что я думаю, происходит, пожалуйста, прокомментируйте это, если вы согласны / не согласны....

Очевидно, что моя главная проблема заключается в том, что кажется, что этот блок кода выполняется одновременно, создавая повторяющиеся записи (импортируя одну и ту же фотографию дважды), когда это обычно не происходит, если он запускается последовательно. Когда фотография обрабатывается, к ней применяется "грязный" бит, гарантирующий, что при следующем вызове метода он пропускает это изображение, но этого не происходит, и некоторые изображения обрабатываются дважды. Может ли это быть связано с тем, что я перечисляю объекты во второй очереди, используя enumerategroupswithtypes: в пределах этого serialQueue?

  1. вызвать processImages
  2. enumerateObjects
  3. немедленно вернуться из enumerateObjects, поскольку он сам по себе является асинхронным
  4. завершить вызов processImages

processImages на самом деле не выполняется, хотя из-за того факта, что enumerategroups, вероятно, все еще работает, но очередь может решить, что это сделано, поскольку она достигает конца блока до того, как enumerategroups закончит работу. Это кажется возможностью для меня?

5 ответов

Серийные очереди АБСОЛЮТНО будут выполнять поочередно. Однако они не гарантированно работают в одной и той же нити.

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

вот пример:

  1. SQ запускается в потоке X, отправляет "In processImages"
  2. журнал принтов "In proc"
  3. SQ в потоке X отправляет сообщение "Done with processImages"
  4. SQ запускается в потоке Y, отправляет "In processImages"
  5. журнал печатает "essImages\n"

После 5. NSLog не обязательно знает, что печатать, 3. или 4.

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

dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"whatever");
});

Если все вызовы NSlog находятся в одной и той же очереди, у вас не должно быть этой проблемы.

enumerateGroupsWithTypes:usingBlock:failureBlock: выполняет свою работу асинхронно в другом потоке и вызывает переданные блоки, когда это сделано (в основном потоке, я думаю). Если посмотреть на это с другой точки зрения, если он завершит все синхронно ко времени завершения вызова метода, он может просто вернуть объект-перечислитель групп, например, для более простого API.

Из документации:

Этот метод является асинхронным. Когда перечисляются группы, пользователю может быть предложено подтвердить доступ приложения к данным; метод, однако, возвращается немедленно. Вы должны выполнять любую работу с активами в enumerationBlock.

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

Если вопрос "Может ли последовательная очередь выполнять задачи асинхронно?" тогда ответ - нет. Если вы считаете, что это возможно, вам следует убедиться, что все задачи действительно выполняются в одной и той же очереди. Вы можете добавить следующую строку в блок и сравнить вывод:

dispatch_async(self.serialQueue, ^{
    NSLog(@"current queue:%p current thread:%@",dispatch_get_current_queue(),[NSThread currentThread]);

Убедитесь, что вы пишете NSLog в блоке, который выполняется в вашей очереди, а не в enumerateGroupsWithTypes: usingBlock: failBlock: Также вы можете попытаться создать свою очередь следующим образом

dispatch_queue_create("label", DISPATCH_QUEUE_SERIAL);

но я не думаю, что это что-то изменит

РЕДАКТИРОВАТЬ: Кстати, метод

enumerateGroupsWithTypes:usingBlock:failureBlock:

асинхронный, почему вы вызываете его в другой очереди?

ОБНОВЛЕНИЕ 2: я могу предложить что-то вроде этого:

dispatch_async(queue, ^{
    NSLog(@"queue");

    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER, *pmutex = &mutex;
    pthread_mutex_lock(pmutex);

    ALAssetsLibraryGroupsEnumerationResultsBlock listGroupBlock = ^(ALAssetsGroup *group, BOOL *stop) {
        NSLog(@"block");
        if (group) {
            [groups addObject:group];
        } else {

            [self.tableView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO];
            dispatch_async(dispatch_get_current_queue(), ^{
                pthread_mutex_unlock(pmutex);
            });
        }
        NSLog(@"block end");
    };

    [assetsLibrary enumerateGroupsWithTypes:groupTypes usingBlock:listGroupBlock failureBlock:failureBlock];
    pthread_mutex_lock(pmutex);
    pthread_mutex_unlock(pmutex);
    pthread_mutex_destroy(pmutex);
    NSLog(@"queue end");
});

Использование Swift и семафоров для иллюстрации подхода к сериализации:

Дано: класс с асинхронным методом 'run', который будет запускаться на нескольких объектах одновременно, и цель состоит в том, чтобы каждый из них не запускался до тех пор, пока он не завершится.

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

Идея такова: если используется последовательная очередь, одновременно будет работать только одна, одна за другой.

Создайте последовательную очередь в глобальном пространстве по классу:

let serialGeneratorQueue: DispatchQueue = DispatchQueue(label: "com.limit-point.serialGeneratorQueue", autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.workItem)

class Generator {

    func run() {
         asynchronous_method()
    }

    func start() {

        serialGeneratorQueue.async {
            self.run()
        }
    }

    func completed() {
       // to be called by the asynchronous_method() when done
    }
}

Метод run этого класса, для которого будет создано и запущено очень много объектов, будет обработан в последовательной очереди:

serialGeneratorQueue.async {
    self.run()
}

В этом случае autoreleaseFrequency - это.workItem для очистки памяти после каждого запуска.

Метод run имеет некоторую общую форму:

func run() {
   asynchronous_method()
}

Проблема в следующем: метод run завершается до завершения asynchronous_method, запускается следующий метод run в очереди и т. Д. Таким образом, цель не достигается, поскольку каждый asynchronous_method выполняется параллельно, а не последовательно.

Используйте семафор, чтобы исправить. В классе объявляют

let running = DispatchSemaphore(value: 0)

Теперь asynchronous_method завершает работу, вызывая "завершенный" метод:

func completed() {
   // some cleanup work etc.
}

Семафор можно использовать для сериализации цепочки asynchronous_method, добавив метод running.wait() в метод run:

func run() {
    asynchronous_method()

    running.wait() 
}

И затем в метод complete () добавьте 'running.signal()'

func completed() {
   // some cleanup work etc.

    running.signal()
}

Функция running.wait() в 'run' будет препятствовать выходу из нее до тех пор, пока не будет получен сигнал завершенного метода с помощью running.signal(), что, в свою очередь, не позволяет последовательной очереди запустить следующий метод run в очереди. Таким образом, цепочка асинхронных методов действительно будет выполняться последовательно.

Итак, теперь класс имеет вид:

class Generator {

    let running = DispatchSemaphore(value: 0)

    func run() {
         asynchronous_method()

         running.wait() 
    }

    func start() {

        serialGeneratorQueue.async {
            self.run()
        }
    }

    func completed() {
       // to be called by the asynchronous_method() when done

       running.signal()
    }
}

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

Таким образом, вы должны обернуть все вызовы внутри метода main с явным dispatch_async(serializedQueue, ^{}) чтобы убедиться, что все сделано в правильном порядке...

Я думал, что последовательная очередь будет ждать [пока] не будет выполнен первый блок...

Оно делает. Но ваш первый блок просто вызываетenumerateGroupsWithTypesи документация предупреждает нас, что метод работает асинхронно:

Этот метод асинхронный. При перечислении групп пользователя могут попросить подтвердить доступ приложения к данным; однако метод возвращается немедленно.

(FWIW, всякий раз, когда вы видите метод с параметром блокировки / закрытия, это красный флаг, указывающий на то, что метод, вероятно, выполняет что-то асинхронно. Вы всегда можете обратиться к документации соответствующего метода и подтвердить, как мы здесь.)

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

Итак, если вам действительно нужно, чтобы каждая задача ожидала выполнения предыдущей асинхронной задачи, существует ряд традиционных решений этой проблемы:

  1. Используйте рекурсивный шаблон. Т.е. напишите исполнениеprocessImage который требует обработки массива изображений и:

    • проверьте, есть ли изображения для обработки;
    • обработать первое изображение; а также
    • когда закончите (то есть в блоке обработчика завершения), удалите первое изображение из массива, а затем вызовите processImage еще раз.
  2. Вместо отправки очередей рассмотрите возможность использования очередей операций. Затем вы можете реализовать свою задачу как "асинхронную" NSOperation подкласс. Это очень элегантный способ упаковки асинхронной задачи. Это показано в /questions/38327179/nsurlsession-s-nsblockoperation-i-ocheredyami/38327197#38327197.

  3. Вы можете использовать семафоры, чтобы эта асинхронная задача работала синхронно. Это также показано в /questions/38327179/nsurlsession-s-nsblockoperation-i-ocheredyami/38327197#38327197.

Вариант 1 - самый простой, вариант 2 - самый элегантный, а вариант 3 - хрупкое решение, которого по возможности следует избегать.

У вас может быть несколько объектов, каждый со своей последовательной очередью. Задачи, отправленные в любую последовательную очередь, выполняются последовательно, но задачи, отправляемые в разные последовательные очереди, будут абсолютно чередоваться.

Еще одна простая ошибка - создание не последовательной очереди, а параллельной очереди...

Другие вопросы по тегам