问题 在iOS 3.x中等效的GCD串行调度队列


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

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


3256
2018-05-27 22:50


起源



答案:


似乎人们需要花费大量精力来重写NSRunloop。按照 NSRunloop文档

您的应用程序无法创建   或明确管理NSRunLoop   对象。每个NSThread对象,   包括应用程序的主要内容   thread,有一个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定义了一个自定义输入源   允许您执行选择器   在任何线程上。 ...执行选择器请求   在目标线程上序列化,   缓解许多人   可能的同步问题   在运行多个方法时发生   在一个线程上。

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


3
2017-07-05 21:25





这个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];

4
2018-06-06 21:43



有点复杂,并一直到下去 NSThread 水平,但看起来它会工作(没有尝试过)。尽管如此,似乎应该采用不那么复杂的方式来做到这一点...... - jrdioko


答案:


似乎人们需要花费大量精力来重写NSRunloop。按照 NSRunloop文档

您的应用程序无法创建   或明确管理NSRunLoop   对象。每个NSThread对象,   包括应用程序的主要内容   thread,有一个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定义了一个自定义输入源   允许您执行选择器   在任何线程上。 ...执行选择器请求   在目标线程上序列化,   缓解许多人   可能的同步问题   在运行多个方法时发生   在一个线程上。

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


3
2017-07-05 21:25





这个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];

4
2018-06-06 21:43



有点复杂,并一直到下去 NSThread 水平,但看起来它会工作(没有尝试过)。尽管如此,似乎应该采用不那么复杂的方式来做到这一点...... - jrdioko


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

编辑

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

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

我不怕编写线程程序,所以这里有一个解决方案:

  • 创建一个fifo工作队列:
    • 支持锁定
    • 拥有一个NSOperationQueue
    • 拥有一个NSOperation子类,用于在其实现中从fifo队列中提取工作者 main。一次只能存在一个。
    • 持有一个工作的NSArray(定义一个工人取决于你 - 它是一个NSInvocation,类,操作,......)

NSOperation子类将工作程序从fifo工作队列中拉出,直到fifo工作队列耗尽为止。

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

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

祝你好运


3
2018-05-27 22:57



我引用的句子在段落中解释了如何“序列化”a NSOperationQueue 通过将最大并发操作设置为1.但似乎可以说,与串行调度队列相比,不能保证任务先执行,先执行。 - jrdioko
困了我,不读整件事情=)会更新 - justin
@jrdioko你可以创建一个子类 NSOperationQueue 这保证了它添加的任何操作都不具有任何依赖性......实际上,除非你自己明确地设置它们,否则你 是 保证它是FIFO。 - Dave DeLong
如果我没有任何依赖关系/优先级,它是否保证是FIFO?我在文档中没有看到。 - jrdioko
是的 - 但你必须这样设计。客户端将工作程序添加到fifo工作队列。然后,fifo工作队列将工作人员出售给NSOperation子类,直到它没有工人来销售。那时,NSOperation子类告诉fifo worker队列它正在退出。 fifo worker队列确保在任何给定时间只运行一个(或零)NSOperation子类。设计工作人员,使他们不依赖于依赖关系或优先级。如果您编写自己的工作者或使用NSInvocation,这是免费的。如果你选择NSOperations(续) - justin


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

将最大并发操作数设置为1可保证仅为串行 如果NSOperations从同一个线程添加到队列中。

我正在使用另一个选项,因为它只是有效。

从不同的线程添加NSOperations,但使用NSCondition来管理排队。 使用performSelectorOnBackgroundThread调用startOperations(并且应该不希望用锁来阻止主线程)...

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

而已!


2
2018-06-03 04:56



如果一次添加所有操作,看起来很好,但我想的是在不同时间从不同位置任意添加操作的情况。 - jrdioko
没关系,1或10次操作。三个操作对同一个对象执行不同的操作,因为对于此特定示例,我将代码拉出来的分段任务不合逻辑。如果您需要为每个对象执行一个特定任务,请继续... - TheBlack


如果处理是在后台,你真的需要严格按顺序进行处理吗?如果这样做,只需设置依赖关系就可以达到同样的效果,因此1取决于0,2 on 1,3 on on 2等等。然后强制操作队列按顺序处理它们。将最大并发操作数设置为1,并且队列也保证是串行的。


0
2018-06-02 20:04



我可以想一想它需要严格按顺序排列的情况。如果所有任务都可用并且同时排队,那么设置这样的依赖关系会起作用,但我正在考虑随意添加它们。 - jrdioko