11

Apple 的 Grand Central Dispatch (GCD) 很棒,但仅适用于 iOS 4.0 或更高版本。Apple 的文档说,“[A] 序列化操作队列不提供与 Grand Central Dispatch 中的串行调度队列完全相同的行为”(因为队列不是 FIFO,但顺序由依赖关系和优先级决定)。

在支持 GCD 发布之前的 OS 版本的同时,实现与 GCD 的串行调度队列相同的效果的正确方法是什么?或者换一种说法,在想要支持低于 4.0 版本的 iOS 应用程序中处理简单后台处理(进行 Web 服务请求等)的推荐方法是什么?

4

5 回答 5

4

这个 PseudoSerialQueue 怎么样?它是像 Dispatch Serial Queue 一样的最小实现。

#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];
于 2011-06-06T21:43:06.287 回答
3

似乎人们会付出很多努力来重写 NSRunloop。根据NSRunloop 文档

您的应用程序不能创建或显式管理 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];

根据Run Loops 上的线程编程指南部分

Cocoa 定义了一个自定义输入源,允许您在任何线程上执行选择器。...执行选择器请求在目标线程上进行序列化,从而减轻了在一个线程上运行多个方法时可能出现的许多同步问题。

所以你有一个明确的串行队列。当然,我的写得并不出色,因为我已经告诉运行循环永远运行,你可能更喜欢一个你可以稍后终止的,但这些很容易修改。

于 2011-07-05T21:25:24.593 回答
3

您可以使用 来模拟它NSOperationQueue,然后只需将任务计数设置为 1。

编辑

- 哎呀,应该更仔细地阅读。fifo 解决方案如下:

我想不出大多数 ios 开发人员会在您的情况下使用的方法。

我不怕编写线程程序,所以这是一种解决方案:

  • 创建一个先进先出工作队列:
    • 支持锁定
    • 持有一个 NSOperationQueue
    • 拥有一个 NSOperation 子类,旨在在main. 一次只能存在一个。
    • 持有要运行的工作人员的 NSArray(定义工作人员取决于您 - 它是 NSInvocation、类、操作......)

NSOperation 子类从 fifo 工作队列中拉出工作人员,直到 fifo 工作队列用完。

当 fifo 工作队列有工人并且没有活动的子操作时,它会创建一个子操作,并将其添加到其操作队列中。

如果您不习惯编写线程程序,则会有一些陷阱——因此,此解决方案并不适合所有人,但如果您已经习惯使用所有所需的技术,则编写此解决方案不会花费很长时间。

祝你好运

于 2011-05-27T22:57:21.133 回答
2

有些事情 NSOperationQueue 文档作者忘记提及,使得这样的实现看起来微不足道,而实际上并非如此。

仅当从同一线程将 NSOperations 添加到队列中时,才能保证将最大并发操作计数设置为 1 是串行的。

我正在使用另一个选项,因为它可以正常工作。

从不同的线程添加 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];
}

而已!

于 2011-06-03T04:56:19.650 回答
0

如果无论如何处理都在后台,你真的需要它严格有序吗?如果你这样做了,你可以简单地通过设置你的依赖来达到同样的效果,所以 1 依赖于 0、2 依赖于 1、3 依赖于 2 等等。然后强制操作队列按顺序处理它们。设置最大并发操作数为1,队列也保证是串行的。

于 2011-06-02T20:04:37.340 回答