ios - 如何将代码块分派(dispatch)到 iOS 中的同一个线程?

标签 ios asynchronous sqlite thread-safety grand-central-dispatch

问题的主要方面:是关于 iOS 的。我能否以某种方式分派(dispatch)代码块,使它们都 (a) 在后台运行并且 (b) 在同一线程上运行?我想在后台运行一些耗时的操作,但这些操作必须在同一个线程上运行,因为它们涉及资源,不能在线程之间共享。

更多技术细节,如果需要的话:它是关于为 Apache Cordova 实现一个 sqlite 插件,Apache Cordova 是移动平台上 HTML5 应用程序的框架。这个插件应该是 WebSQL 的一个实现以 Cordova 的插件 API 的方式。 (这意味着,不可能将整个交易包装在单个 block 中,这可以使一切变得更容易。)

这是 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];
    }];
}

但据我所知,无法保证那些分派(dispatch)的代码块(参见 runInBackground)将在同一线程上运行。

最佳答案

GCD 不保证两个 block 在同一个线程上运行,即使它们属于同一个队列(当然主队列除外)。但是,如果您使用的是串行队列 (DISPATCH_QUEUE_SERIAL),这不是问题,因为您知道没有并发访问您的数据。

dispatch_queue_create 的手册页说:

Queues are not bound to any specific thread of execution and blocks submitted to independent queues may execute concurrently.

我不知道有什么方法可以将队列绑定(bind)到特定的线程(毕竟,不需要关心线程是 GCD 的一个重点)。 您可以使用串行队列而不用担心实际线程的原因是这个 promise :

All memory writes performed by a block dispatched to a serial queue are guaranteed to be visible to subsequent blocks dispatched to the same queue.

也就是说,似乎使用了内存屏障。

在处理线程问题时,您主要关心的通常是避免两个线程同时访问某些内容。如果您使用的是串行队列,则不会遇到此问题。 哪个线程正在访问您的资源通常并不重要。例如,我们使用串行队列来毫无问题地管理核心数据访问。

编辑:

看来您确实发现了一种需要在同一个线程上工作的罕见情况。您可以实现自己的工作线程:

  • 先决条件:
    • 一个 NSMutableArray(我们称它为 blockQueue)。
    • 一个 NSCondition(我们称之为 queueCondition)。
  • 创建一个新的 NSThread。
    • 线程的方法有一个无限循环,在这个循环中它锁定条件,如果队列为空(并且“退出” bool 值是假的)等待它,出队一个 block 并执行它。
  • 锁定条件并使 block 排队的方法。

由于这种情况,线程将在没有工作可做时简单地休眠。​​

所以,大致(未经测试,假设 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's 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's 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()
}

关于ios - 如何将代码块分派(dispatch)到 iOS 中的同一个线程?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22091696/

相关文章:

java - 线程限制超出替换阻塞的工作线程

python - 改变表 Sqlite : how to check if a column exists before alter the table?

python - Spotify token 交换 ruby​​ 文件托管

ios - dateFromString在ios 6中工作,但不在ios 5中工作

scala future 错误为 "Don' t 直接调用 `Awaitable` 方法,请使用 `Await` 对象。”

c# - iOS 版 SQLite - RANDOM() 不够好

objective-c - 对 Core Data 数据结果进行分组?

javascript - 如何在 ReactJS 中导入 ActivityIndi​​catorIOS?

ios - 可变大小的结构数组?

node.js - Nodejs - 错误回调的重新调用函数 - 是否有非阻塞方式?