iOS 3.x中GCD串行调度队列的等效方式是什么?

11

苹果的Grand Central Dispatch (GCD)功能很好,但仅适用于iOS 4.0或更高版本。 苹果的文档说:“[一个]串行操作队列并没有像 Grand Central Dispatch 中的串行派发队列一样提供完全相同的行为”(因为队列不是FIFO,而是由依赖和优先级确定顺序)。

如何以支持GCD发布之前的iOS版本的方式实现与GCD串行派发队列相同的效果呢?或者换句话说,iOS应用程序中处理简单的后台处理(执行Web服务请求等)的推荐方法是什么,这些应用程序希望支持低于4.0版本的iOS?

5个回答

4
这个PseudoSerialQueue怎么样?它是一个最小的实现,类似于Dispatch串行队列。
#import <Foundation/Foundation.h>

@interface PseudoTask : NSObject
{
    id target_;
    SEL selector_;
    id queue_;
}

@property (nonatomic, readonly) id target;

- (id)initWithTarget:(id)target selector:(SEL)selector queue:(id)queue;
- (void)exec;
@end

@implementation PseudoTask

@synthesize target=target_;

- (id)initWithTarget:(id)target selector:(SEL)selector queue:(id)queue;
{
    self = [super init];
    if (self) {
        target_ = [target retain];
        selector_ = selector;
        queue_ = [queue retain];
    }
    return self;
}

- (void)exec
{
    [target_ performSelector:selector_];
}

- (void)dealloc
{
    [target_ release];
    [queue_ release];
}
@end

@interface PseudoSerialQueue : NSObject
{
    NSCondition *condition_;
    NSMutableArray *array_;
    NSThread *thread_;
}
- (void)addTask:(id)target selector:(SEL)selector;
@end

@implementation PseudoSerialQueue
- (id)init
{
    self = [super init];
    if (self) {
        array_ = [[NSMutableArray alloc] init];
        condition_ = [[NSCondition alloc] init];
        thread_ = [[NSThread alloc]
            initWithTarget:self selector:@selector(execQueue) object:nil];
        [thread_ start];
    }
    return self;
}

- (void)addTask:(id)target selector:(SEL)selector
{
    [condition_ lock];
    PseudoTask *task = [[PseudoTask alloc]
        initWithTarget:target selector:selector queue:self];
    [array_ addObject:task];
    [condition_ signal];
    [condition_ unlock];
}

- (void)quit
{
    [self addTask:nil selector:nil];
}

- (void)execQueue
{
    for (;;) {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

        [condition_ lock];
        while (array_.count == 0)
            [condition_ wait];
        PseudoTask *task = [array_ objectAtIndex:0];
        [array_ removeObjectAtIndex:0];
        [condition_ unlock];

        if (!task.target) {
            [pool drain];
            break;
        }

        [task exec];
        [task release];

        [pool drain];
    }
}

- (void)dealloc
{
    [array_ release];
    [condition_ release];
}
@end

使用方法:

PseudoSerialQueue *q = [[[PseudoSerialQueue alloc] init] autorelease];
[q addTask:self selector:@selector(test0)];
[q addTask:self selector:@selector(test1)];
[q addTask:self selector:@selector(test2)];
[q quit];

有点复杂,需要一直到NSThread层面,但看起来应该可以工作(还没有尝试过)。不过似乎仍然应该有一种更简单的方法来做到这一点... - jrdioko

3
似乎人们正在努力重写NSRunloop。根据NSRunloop documentation
应用程序无法创建或显式管理NSRunLoop对象。每个NSThread对象(包括应用程序的主线程)都会在需要时自动创建一个NSRunLoop对象。
因此,肯定的答案是创建一个可用的队列:
- (void)startRunLoop:(id)someObject
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    [[NSRunLoop currentRunLoop] run];

    [pool release];
}

...

NSThread *serialDispatchThread = [[NSThread alloc] 
                   initWithTarget:self 
                   selector:@selector(startRunLoop:) 
                   object:nil];
[serialDispatchThread start];

将任务添加到队列中:
[object
    performSelector:@selector(whatever:) 
    onThread:serialDispatchThread
    withObject:someArgument
    waitUntilDone:NO];

根据线程编程指南中关于运行循环的部分
Cocoa定义了一个自定义输入源,允许您在任何线程上执行选择器。...执行选择器请求在目标线程上进行序列化,缓解了可能发生的多个方法在一个线程上运行时出现的许多同步问题。
因此,您拥有一个明确的串行队列。当然,我的写法并不是很好,因为我告诉运行循环永远运行,您可能更喜欢稍后可以终止的队列,但这些都是容易修改的。

3
您可以使用NSOperationQueue进行模拟,然后将任务数量设置为一。 编辑 -- 哎呀,我应该仔细阅读的。下面是fifo解决方案:
我想不出大多数iOS开发人员在您的情况下会使用的方法。
我不怕编写多线程程序,所以这是一个解决方案:
创建一个FIFO工作队列,它:
- 支持锁定 - 持有一个NSOperationQueue - 持有一个NSOperation子类,设计用于在其实现中从fifo队列中提取工作程序。一次只能存在一个。 - 持有要运行的工作程序的NSArray(定义工作程序由您决定——是NSInvocation、类、操作等)
NSOperation子类从FIFO工作队列中提取工作程序,直到FIFO工作队列耗尽。
当FIFO工作队列具有工作程序并且没有活动的子操作时,它会创建一个子操作,并将其添加到其操作队列中。
如果您不熟悉编写多线程程序,则存在一些陷阱——因此,对于每个人来说,这种解决方案并不理想,但是如果您已经熟悉了所需的所有技术,则编写此解决方案不需要花费太长时间。
祝好运

我引用的那句话出现在解释如何通过将最大并发操作设置为1来“序列化” NSOperationQueue 的段落中。但与串行调度队列相反,它似乎表明不能保证任务先进先出执行。 - jrdioko
1
@jrdioko 你可以创建一个 NSOperationQueue 的子类,确保其添加的所有操作都没有任何依赖关系... 实际上,除非你明确地自己设置它们,否则你 已经 确保它是 FIFO。 - Dave DeLong
如果我没有任何依赖关系/优先级,它是否保证是FIFO?在文档中我没有看到这一点。 - jrdioko
是的 - 但你必须以这种方式设计它。客户将工人添加到 FIFO 工作队列中。然后,FIFO 工作队列向 NSOperation 子类提供工人,直到没有工人可提供为止。此时,NSOperation 子类告诉 FIFO 工作队列它正在退出。FIFO 工作队列确保在任何给定时间只有一个(或零个)NSOperation 子类在运行。设计工人,使它们不支持依赖关系或优先级。如果你编写自己的工人或使用 NSInvocation,这是免费的。如果你选择 NSOperations,则(cont) - justin
如果您使用 NSOperationNSInvocation 作为您的工人,则它们可能需要优先级或依赖关系。请注意,从 fifo 工作队列中提取工人的 NSOperation 子类不是工作者。使用 NSInvocation 作为工人,NSOperation 子类说“先进先出(fifo)工作队列,请给我下一个要运行的 NSInvocation”。如果您知道只需要按顺序创建一个需要执行的工作数组,而不是依赖于中央工作队列,在设计上可以显着减少此设计的复杂性(200行?)。 - justin

2

在NSOperationQueue的文档中,有些细节被遗漏了,导致实现起来似乎很简单,但实际上并非如此。

将最大并发操作数设置为1只有在从同一线程向队列添加NSOperations时才能保证串行执行。

我使用另一种选项,因为它能正常工作。

可以从不同的线程添加NSOperations,但使用NSCondition来管理排队。startOperations方法可以(而且应该)使用performSelectorOnBackgroundThread进行调用,这样就不会使用锁阻塞主线程。

startOperations方法代表一个由一个或多个NSOperations组成的单个作业。

- (void)startOperations
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    [[AppDelegate condition] lock];

    while (![[[AppDelegate queue] operations] count] <= 0) 
    {
        [[AppDelegate condition] wait];
    }

    NSOperation *newOperation = [alloc, init]....;
    [[AppDelegate queue] addOperation:newOperation];
    [[AppDelegate queue] waitUntilAllOperationsAreFinished]; // Don't forget this!

    NSOperation *newOperation1 = [alloc, init]....;
    [[AppDelegate queue] addOperation:newOperation1];
    [[AppDelegate queue] waitUntilAllOperationsAreFinished]; // Don't forget this!

    NSOperation *newOperation2 = [alloc, init]....;
    [[AppDelegate queue] addOperation:newOperation2];
    [[AppDelegate queue] waitUntilAllOperationsAreFinished]; // Don't forget this!

    // Add whatever number operations you need for this single job

    [[AppDelegate queue] signal];
    [[AppDelegate queue] unlock];

    [NotifyDelegate orWhatever]

    [pool drain];
}

这就是全部内容了!

如果所有操作都在同一时间添加,那看起来很好,但我考虑的是一种情况,即操作是任意地从不同的地方在不同的时间添加的。 - jrdioko
无论是1个还是10个操作都没关系。三个操作在同一个对象上执行不同的任务,因为对于我提取的这个特定示例来说,分解任务并不合适。如果您需要针对每个对象执行一个特定任务,请继续... - TheBlack

0

如果处理过程本来就在后台进行,那么你真的需要它严格按顺序吗?如果需要,你可以通过设置依赖关系来实现相同的效果,使得1依赖于0,2依赖于1,3依赖于2等等。操作队列将被强制按顺序处理它们。将最大并发操作数设置为1,队列也保证是串行的。


我可以想到一些情况需要严格按顺序执行。如果所有任务都同时可用并排队等待,那么设置这样的依赖关系是可行的,但我考虑的是随意在后面添加它们。 - jrdioko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接