草庐IT

objective-c - 在一个 NSOperationQueue 上抢占 NSOperation 并将 NSOperation 放置到一个单独的 NSOperationQueue 上?

coder 2024-01-26 原文

我有一个应用程序,其中一个长时间运行的进程(> 1 分钟)被放置在 NSOperationQueue(队列 A)上。当队列 A 操作运行时,UI 完全响应,完全符合预期。

但是,我有一种用户可以执行的不同类型的操作,它在完全独立的 NSOperationQueue(队列 B)上运行。

当 UI 事件触发队列 B 上的操作放置时,它必须等到队列 A 上当前正在执行的操作完成之后。这发生在 iPod Touch (MC544LL) 上。

相反,我希望看到的是,放置在队列 B 上的任何操作都会或多或少地立即开始与队列 A 上的操作并行执行。这是我在模拟器上看到的行为。

我的问题分为两部分:

  • 根据可用文档,我在设备上看到的行为是否符合预期?
  • 使用 NSOperation/NSOperationQueue,我如何用放置在队列 B 上的新操作抢占队列 A 上当前正在运行的操作?

注意:通过为队列 A/B 使用 GCD 队列,我可以准确地获得我想要的行为,因此我知道我的设备能够支持我正在尝试做的事情。但是,我真的非常想使用 NSOperationQueue,因为这两个操作都需要可取消。

我有一个简单的测试应用程序:

ViewController 是:

//
//  ViewController.m
//  QueueTest
//

#import "ViewController.h"

@interface ViewController ()

@property (strong, nonatomic) NSOperationQueue *slowQueue;
@property (strong, nonatomic) NSOperationQueue *fastQueue;

@end

@implementation ViewController

-(id)initWithCoder:(NSCoder *)aDecoder
{
    if (self = [super initWithCoder:aDecoder]) {
        self.slowQueue = [[NSOperationQueue alloc] init];
        self.fastQueue = [[NSOperationQueue alloc] init];
    }

    return self;
}

-(void)viewDidLoad
{
    NSLog(@"View loaded on thread %@", [NSThread currentThread]);
}

// Responds to "Slow Op Start" button
- (IBAction)slowOpStartPressed:(id)sender {
    NSBlockOperation *operation = [[NSBlockOperation alloc] init];

    [operation addExecutionBlock:^{
        [self workHard:600];
    }];

    [self.slowQueue addOperation:operation];
}

// Responds to "Fast Op Start" button
- (IBAction)fastOpStart:(id)sender {    
    NSBlockOperation *operation = [[NSBlockOperation alloc] init];

    [operation addExecutionBlock:^{
        NSLog(@"Fast operation on thread %@", [NSThread currentThread]);
    }];

    [self.fastQueue addOperation:operation];
}

-(void)workHard:(NSUInteger)iterations
{
    NSLog(@"SlowOperation start on thread %@", [NSThread currentThread]);

    NSDecimalNumber *result = [[NSDecimalNumber alloc] initWithString:@"0"];

    for (NSUInteger i = 0; i < iterations; i++) {        
        NSDecimalNumber *outer = [[NSDecimalNumber alloc] initWithUnsignedInteger:i];

        for (NSUInteger j = 0; j < iterations; j++) {
            NSDecimalNumber *inner = [[NSDecimalNumber alloc] initWithUnsignedInteger:j];
            NSDecimalNumber *product = [outer decimalNumberByMultiplyingBy:inner];

            result = [result decimalNumberByAdding:product];
        }

        result = [result decimalNumberByAdding:outer];
    }

    NSLog(@"SlowOperation end");
}

@end

我在第一次按下“Slow Op Start”按钮后大约 1 秒后按下“Fast Op Start”按钮后看到的输出是:

2012-11-28 07:41:13.051 QueueTest[12558:907] View loaded on thread <NSThread: 0x1d51ec30>{name = (null), num = 1}
2012-11-28 07:41:14.745 QueueTest[12558:1703] SlowOperation start on thread <NSThread: 0x1d55e5f0>{name = (null), num = 3}
2012-11-28 07:41:25.127 QueueTest[12558:1703] SlowOperation end
2012-11-28 07:41:25.913 QueueTest[12558:3907] Fast operation on thread <NSThread: 0x1e36d4c0>{name = (null), num = 4}

如您所见,第二个操作直到第一个操作完成后才开始执行,尽管事实上这是两个独立的(并且可能是独立的)NSOperationQueues。

我已阅读 Apple Concurrency Guide , 但找不到描述这种情况的任何内容。我还阅读了两个关于相关主题的 SO 问题( linklink ),但似乎都没有触及我所看到的问题的核心(先发制人)。

我尝试过的其他事情:

  • 在每个 NSOperation 上设置 queuePriority
  • 在每个 NSOperation 上设置 queuePriority,同时将两种类型的操作放在同一个队列中
  • 将两个操作放在同一个队列中

这个问题经过了多次编辑,这可能会使某些评论/答案难以理解。

最佳答案

我怀疑您遇到的问题是两个操作队列都在底层默认优先级调度队列上执行它们的 block 。因此,如果几个慢速操作在快速操作之前排队,那么您可能会看到这种行为。

为什么不为慢速操作设置 NSOperationQueue 实例,使其在任何给定时间只执行一个操作(即将此队列的 maxConcurrentOperationCount 设置为 1),或者如果您的操作都是 block ,那么为什么不直接使用 GCD 队列?例如

static dispatch_queue_t slowOpQueue = NULL;
static dispatch_queue_t fastOpQueue = NULL;

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    slowOpQueue = dispatch_queue_create("Slow Ops Queue", NULL);
    fastOpQueue = dispatch_queue_create("Fast Ops Queue", DISPATCH_QUEUE_CONCURRENT);
});

for (NSUInteger slowOpIndex = 0; slowOpIndex < 5; slowOpIndex++) {
    dispatch_async(slowOpQueue, ^(void) {
        NSLog(@"* Starting slow op %d.", slowOpIndex);
        for (NSUInteger delayLoop = 0; delayLoop < 1000; delayLoop++) {
            putchar('.');
        }

        NSLog(@"* Ending slow op %d.", slowOpIndex);
    });
}

for (NSUInteger fastBlockIndex = 0; fastBlockIndex < 10; fastBlockIndex++) {
    dispatch_async(fastOpQueue, ^(void) {
        NSLog(@"Starting fast op %d.", fastBlockIndex);
        NSLog(@"Ending fast op %d.", fastBlockIndex);
    });
}

至于根据您关于需要操作取消工具等的评论使用 NSOperationQueue,您可以尝试:

- (void)loadSlowQueue
{
    [self.slowQueue setMaxConcurrentOperationCount:1];
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"begin slow block 1");

        [self workHard:500];

        NSLog(@"end slow block 1");
    }];

    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"begin slow block 2");

        [self workHard:500];

        NSLog(@"end slow block 2");
    }];

    [self.slowQueue addOperation:operation];
    [self.slowQueue addOperation:operation2];
}

因为我认为您添加到慢队列操作的两个 block 正在默认队列上并行执行并阻止您的快速操作被调度。

编辑:

如果您仍然发现默认的 GCD 队列令人窒息,为什么不创建一个 NSOperation 子类来执行 block 而不使用 GCD 来处理您的慢速操作,这仍然会给您带来声明式的便利,无需为每个操作,但使用常规 NSOperation 的线程模型。例如

#import <Foundation/Foundation.h>

typedef void (^BlockOperation)(NSOperation *containingOperation);

@interface PseudoBlockOperation : NSOperation

- (id)initWithBlock:(BlockOperation)block;
- (void)addBlock:(BlockOperation)block;

@end

然后是实现:

#import "PseudoBlockOperation.h"

@interface PseudoBlockOperation()

@property (nonatomic, strong) NSMutableArray *blocks;

@end

@implementation PseudoBlockOperation

@synthesize blocks;

- (id)init
{
    self = [super init];

    if (self) {
        blocks = [[NSMutableArray alloc] initWithCapacity:1];
    }

    return self;
}

- (id)initWithBlock:(BlockOperation)block
{
    self = [self init];

    if (self) {
        [blocks addObject:[block copy]];
    }

    return self;
}

- (void)main
{
    @autoreleasepool {
        for (BlockOperation block in blocks) {
            block(self);
        }
    }
}

- (void)addBlock:(BlockOperation)block
{
    [blocks addObject:[block copy]];
}

@end

然后在你的代码中你可以做这样的事情:

PseudoBlockOperation *operation = [[PseudoBlockOperation alloc] init];
[operation addBlock:^(NSOperation *operation) {
    if (!operation.isCancelled) {
        NSLog(@"begin slow block 1");

        [self workHard:500];

        NSLog(@"end slow block 1");
    }
}];

[operation addBlock:^(NSOperation *operation) {
    if (!operation.isCancelled) {
        NSLog(@"begin slow block 2");

        [self workHard:500];

        NSLog(@"end slow block 2");
    }
}];

[self.slowQueue addOperation:operation];

请注意,在此示例中,添加到同一操作的任何 block 都将按顺序执行而不是并发执行,以同时执行为每个 block 创建一个操作。这比 NSBlockOperation 的优势在于您可以通过更改 BlockOperation 的定义将参数传递到 block 中 - 在这里我传递了包含操作,但您可以传递任何其他需要的上下文。

希望对您有所帮助。

关于objective-c - 在一个 NSOperationQueue 上抢占 NSOperation 并将 NSOperation 放置到一个单独的 NSOperationQueue 上?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13568756/

有关objective-c - 在一个 NSOperationQueue 上抢占 NSOperation 并将 NSOperation 放置到一个单独的 NSOperationQueue 上?的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  3. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  4. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  5. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

    我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

  6. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  7. ruby - 为什么 SecureRandom.uuid 创建一个唯一的字符串? - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?

  8. ruby-on-rails - Rails - 从另一个模型中创建一个模型的实例 - 2

    我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案

  9. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  10. ruby - 一个 YAML 对象可以引用另一个吗? - 2

    我想让一个yaml对象引用另一个,如下所示:intro:"Hello,dearuser."registration:$introThanksforregistering!new_message:$introYouhaveanewmessage!上面的语法只是它如何工作的一个例子(这也是它在thiscpanmodule中的工作方式。)我正在使用标准的ruby​​yaml解析器。这可能吗? 最佳答案 一些yaml对象确实引用了其他对象:irb>require'yaml'#=>trueirb>str="hello"#=>"hello"ir

随机推荐