Как отправлять блоки кода в один поток в iOS?

Основной аспект вопроса: Это о iOS. Могу ли я каким-то образом отсылать блоки кода таким образом, чтобы они все (а) выполнялись в фоновом режиме и (б) в том же потоке? Я хочу запустить некоторые трудоемкие операции в фоновом режиме, но их нужно запускать в одном потоке, потому что они связаны с ресурсами, которые не должны быть разделены между потоками.

Дальнейшие технические подробности, если требуется: Это о внедрении плагина sqlite для Apache Cordova, рамки для приложений HTML5 на мобильных платформах. Этот плагин должен быть реализацией WebSQL в средствах API плагинов Cordova. (Это означает, что невозможно обернуть целые транзакции в рамках отдельных блоков, что может сделать все проще.)

Вот код из Кордовы:

- (void)myPluginMethod:(CDVInvokedUrlCommand*)command
{
    // Check command.arguments here.
    [self.commandDelegate runInBackground:^{
        NSString* payload = nil;
        // Some blocking logic...
        CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:payload];
        // The sendPluginResult method is thread-safe.
        [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
    }];
}

Но, насколько я знаю, нет гарантии, что отправленные кодовые блоки (см. runInBackground) будут запускаться в одном потоке.

Ответ 1

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

Страница man для dispatch_queue_create говорит:

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

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

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

То есть барьер памяти, похоже, используется.

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

Редактировать:

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

  • Предпосылки:
    • NSMutableArray (пусть его называют blockQueue).
    • Условие NSC (пусть оно называется queueCondition).
  • Создайте новый NSThread.
    • Метод потока имеет бесконечный цикл, в котором он блокирует условие, ожидает его, если очередь пуста (а bool "quit" имеет значение false), удаляет блок из очереди и выполняет его.
  • Метод, который блокирует условие и ставит блок в очередь.

Из-за условия поток просто будет спать, пока не будет работы.

Итак, примерно (не проверено, при условии ARC):

- (void)startWorkerThread
{
    workerThread = [[NSThread alloc]
        initWithTarget:self
        selector:@selector(threadMain)
        object:nil
    ];
    [workerThread start];
}

- (void)threadMain
{
    void (^block)();
    NSThread *currentThread;

    currentThread = [NSThread currentThread];

    while (1) {
        [queueCondition lock];
        {
            while ([blockQueue count] == 0 && ![currentThread isCancelled]) {
                [queueCondition wait];
            }

            if ([currentThread isCancelled]) {
                [queueCondition unlock];
                return;
            }

            block = [blockQueue objectAtIndex:0];
            [blockQueue removeObjectAtIndex:0];
        }
        [queueCondition unlock];

        // Execute block outside the condition, since it also a lock!
        // We want to give other threads the possibility to enqueue
        // a new block while we're executing a block.
        block();
    }
}

- (void)enqueue:(void(^)())block
{
    [queueCondition lock];
    {
        // Copy the block! IIRC you'll get strange things or
        // even crashes if you don't.
        [blockQueue addObject:[block copy]];
        [queueCondition signal];
    }
    [queueCondition unlock];
}

- (void)stopThread
{
    [queueCondition lock];
    {
        [workerThread cancel];
        [queueCondition signal];
    }
    [queueCondition unlock];
}

Непроверенный порт Swift 5:

var workerThread: Thread?
var blockQueue = [() -> Void]()
let queueCondition = NSCondition()

func startWorkerThread() {
    workerThread = Thread() {
        let currentThread = Thread.current
        while true {
            self.queueCondition.lock()
            while self.blockQueue.isEmpty && !currentThread.isCancelled {
                self.queueCondition.wait()
            }

            if currentThread.isCancelled {
                self.queueCondition.unlock()
                return
            }

            let block = self.blockQueue.remove(at: 0)
            self.queueCondition.unlock()

            // Execute block outside the condition, since it also a lock!
            // We want to give other threads the possibility to enqueue
            // a new block while we're executing a block.
            block()
        }
    }
    workerThread?.start()
}

func enqueue(_ block: @escaping () -> Void) {
    queueCondition.lock()
    blockQueue.append(block)
    queueCondition.signal()
    queueCondition.unlock()
}

func stopThread() {
    queueCondition.lock()
    workerThread?.cancel()
    queueCondition.signal()
    queueCondition.unlock()
}

Ответ 2

В GCD: нет, это невозможно с текущей доставкой lib.

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

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

Пожалуйста, напишите запрос функции Apple, поскольку он кажется оправданным и звуковым. Но я боюсь, что это невозможно, иначе оно уже существует;)

Ответ 3

Вы можете использовать NSOperationQueue. Вы можете использовать только один поток, используя метод - (void)setMaxConcurrentOperationCount:(NSInteger)count. Установите его на 1

Ответ 4

Вы можете использовать NSOperationQueue с MaxConcurrentOperationCount of 1 или пойти по ручному пути по дороге, используя модель NSThread (а не Grand Central Dispatch). Используя последнее, я бы рекомендовал вам реализовать рабочий метод, который работает в потоке и вытаскивает рабочие пакеты (или команды) из очереди или пула, который подается из-за пределов потока. Просто убедитесь, что вы используете Locks/Mutex/Synchronization.

Ответ 5

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

    @property (strong, atomic) dispatch_queue_t downloadQueue;

Queue/Thread 1 для первой операции

    downloadQueue = dispatch_queue_create("operation1", NULL);

и др.

Так как atomic является потокобезопасным, downloadQueue не должен быть доступен другим потокам. Поэтому он гарантирует, что на операцию будет только один поток, а другие потоки не будут получать к нему доступ.

Ответ 6

Создайте очередную диспетчерскую очередь и отправьте все вызовы в эту очередь последовательной отправки. Все вызовы будут выполняться в фоновом режиме, но последовательно в одном потоке.

Ответ 7

Если вы хотите выполнить селектор в главной теме, вы можете использовать

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait

и если вы хотите выполнить его в фоновом потоке

- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)object

и если вы хотите выполнить в любом другом потоке, используйте GCD (Grand Central Dispatch)

double delayInSeconds = 2.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        //code to be executed on the main queue after delay
    });

Ответ 8

Так же, как это,

dispatch_asyn(dispatch_get_current_queue, ^ {
});