如何将代码块分派到iOS中的同一个线程?

时间:2014-02-28 09:53:33

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

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

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

以下是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)将在同一个线程上运行。

8 个答案:

答案 0 :(得分:18)

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

dispatch_queue_create的手册页说:

  

队列没有绑定到任何特定的执行线程,并且提交给独立队列的块可以同时执行。

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

  

分派到串行队列的块执行的所有内存写入都保证对分派到同一队列的后续块可见。

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

在处理线程问题时,您主要关心的是避免两个线程同时访问某些内容。如果您使用的是串行队列,则不会出现此问题。 哪个线程正在访问您的资源通常并不重要。例如,我们使用串行队列来管理Core Data访问而没有任何问题。

修改

看起来你真的找到了一个罕见的情况,你需要在同一个线程上工作。您可以实现自己的工作线程:

  • 先决条件:
    • 一个NSMutableArray(我们称之为blockQueue)。
    • 一个NSCondition(我们称之为queueCondition)。
  • 创建新的NSThread。
    • 线程的方法有一个无限循环,它在其中锁定条件,如果队列为空(等待“退出”bool为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'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];
}

答案 1 :(得分:2)

在GCD中:不,对于当前的lib调度,这是不可能的。

可以通过调度lib在任何可用的线程上执行块,无论它们被分派到哪个队列。

一个例外是主队列,它总是在主线程上执行它的块。

请向Apple提交功能请求,因为它看似合理且合理。但我担心这不可行,否则它就已存在;)

答案 2 :(得分:0)

您可以使用NSOperationQueue。您可以使用方法- (void)setMaxConcurrentOperationCount:(NSInteger)count使其仅使用一个线程。将其设置为1

答案 3 :(得分:0)

您可以NSOperationQueue MaxConcurrentOperationCount 1使用NSThread,也可以使用{{1}}模式(而不是Grand Central Dispatch)手动使用。 使用后者我建议你实现一个在线程中运行的worker-method,并从正在从线程外部提供的队列或池中提取工作包(或命令)。只需确保使用Locks / Mutex / Synchronization。

答案 4 :(得分:0)

从未尝试过这个,但这可能会成功。为每个操作使用atomic调度队列的单独属性。

    @property (strong, atomic) dispatch_queue_t downloadQueue;

第一次操作的队列/线程

    downloadQueue = dispatch_queue_create("operation1", NULL);

由于atomic是线程安全的,因此其他线程不应访问downloadQueue。因此,它确保每个操作只有一个线程,而其他线程不会访问它。

答案 5 :(得分:-1)

创建一个串行调度队列,并将所有调用分派给该串行调度队列。所有调用都将在后台执行,但顺序在同一个线程上执行。

答案 6 :(得分:-1)

如果要在主线程中执行选择器,可以使用

- (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
    });

答案 7 :(得分:-2)

就像这样,

dispatch_asyn(dispatch_get_current_queue, ^ {
});