在后台绘制SpriteKit对象的BFTask是锁定主线程

时间:2016-01-19 17:48:58

标签: objective-c multithreading parse-platform sprite-kit bftask

我正在使用BFTasks在后台执行一些SpriteKit绘图,但我不确定我是否正确使用它们,因为绘图正在锁定主线程。

每个对象由几个SKSpriteNodes组成,这些SKSpriteNodes在渲染之前被展平。我希望每个人一旦被展平就进行渲染,即当我调用[self addChild:miniNode];时它会等到所有人都被创建,(锁定主线程)然后它们一下子出现。

我简化了下面的代码以显示任务链:

- (void)drawLocalRelationships
{
    [ParseQuery getLocalRelationships:_player.relationships block:^(NSArray *objects, NSError *error) {
        [[[self drawRelationships:objects forMini:_player]
          continueWithBlock:^id(BFTask *task) {
              //this continues once they've all been drawn and rendered 
              return nil;
          }];
    }];
}

- (BFTask *)drawRelationships:(NSArray *)relationships forMini:(Mini *)mini
{
    return [_miniRows drawSeriesRelationships:relationships forMini:mini];
}

MiniRows类:

- (BFTask *)drawSeriesRelationships:(NSArray *)relationships forMini:(Mini *)mini
{
    BFTask *task = [BFTask taskWithResult:nil];

    for (Relationship *relationship in relationships) {
        task = [task continueWithBlock:^id(BFTask *task) {
            return [self drawRelationship:relationship mini:mini];
        }];
    }
    return task;
}

- (BFTask *)drawRelationship:(Relationship *)relationship mini:(Mini *)mini
{
    //code to determine 'row'
    return [row addMiniTask:otherMini withRelationship:relationship];
}

Row类:

- (BFTask *)addMiniTask:(Mini*)mini withRelationship:(Relationship *)relationship
{
    //drawing code
    MiniNode *miniNode = [self nodeForMini:mini size:size position:position scale:scale];
    [self addChild:miniNode]; //doesn't actually render here
    return [BFTask taskWithResult:nil];
}

我已经尝试在后台线程上运行addMiniTask方法,但它似乎没有什么区别。我想知道我是否误解了BFTasks的概念 - 我认为它们会自动在后台线程上运行,但也许不是?

1 个答案:

答案 0 :(得分:3)

默认情况下,BFTasks不会在后台线程上运行!

如果你这样做:

BFTask * immediateTask = [BFTask taskWithResult: @"1"];

immediateTask完成,即完成的属性为YES,立即在当前线程中。

另外,如果你这样做:

[task continueWithBlock:^id(BFTask *task) {
    // some long running operation 
    return nil;
}];

一旦任务完成,该块在默认执行器中执行,该执行器立即在当前线程中执行块,除非调用堆栈太深,在这种情况下它被卸载到后台调度队列。 当前线程是调用continueWithBlock的线程。 因此,除非您在后台线程中调用前面的代码,否则长时间运行的操作将阻止当前线程。

但是,您可以使用显式执行程序将块卸载到不同的线程或队列:

BFTask * task = [BFTask taskFromExecutor:executor withBlock:^id {
    id result = ...; // long computation
    return result;
}];

选择正确的执行者至关重要:

  • executor = [BFExecutor defaultExecutor]任务的块在当前线程(执行任务创建的线程)上运行,或者如果调用堆栈太深则卸载到后台队列。因此,很难预测会发生什么;
  • executor = [BFExecutor immediateExecutor]任务的块在与前一个任务相同的线程上运行(参见下面的链接)。但如果上一个任务是由默认执行程序运行的,那么你真的不知道它是哪个线程;
  • executor = [BFExecutor mainThreadExecutor]任务的块在主线程上运行。这是在长时间运行操作后用于更新UI的文件。
  • executor = [BFExecutor executorWithDispatchQueue:gcd_queue]任务块在提供的gcd队列中运行。使用后台队列创建一个以执行长时间运行的操作。队列的类型(串行或并发)取决于要执行的任务及其依赖性。

根据执行者的不同,您会得到不同的行为。

BFTasks的优势在于您可以链接和同步在不同线程中运行的任务。例如,要在长时间运行后台操作后更新主线程中的UI,您可以执行以下操作:

// From the UI thread
BFTask * backgroundTask = [BFTask taskFromExecutor:backgroundExecutor withBlock:^id {
    // do your long running operation here
    id result = ...; // long computation here
    return result;
}];
[backgroundTask continueWithExecutor:[BFExecutor mainThreadExecutor] withSuccessBlock:^id(BFTask* task) {
    id result = task.result;
    // do something quick with the result - we're executing in the UI thread here
    return nil
}];

PFQuery findInBackgroundWithBlock方法使用默认执行程序执行块,因此如果从主线程调用该方法,则块很可能也会在主线程中执行。 在你的情况下,虽然我对SpriteKit一无所知,但我会获取所有精灵,然后更新UI:

- (void)queryRenderAllUpdateOnce {

    NSThread *currentThread = [NSThread currentThread];
    NSLog(@"current thread is %@ ", currentThread);

    // replace the first task by [query findObjectsInBackground]
    [[[BFTask taskFromExecutor:[Tasks backgroundExecutor] withBlock:^id _Nonnull{

        NSLog(@"[%@] - Querying model objects", [NSThread currentThread]);
        return @[@"Riri", @"Fifi", @"LouLou"];

    }] continueWithExecutor:[BFExecutor immediateExecutor] withBlock:^id _Nullable(BFTask * _Nonnull task) {

        NSLog(@"[%@] - Fetching sprites for model objects", [NSThread currentThread]);
        NSArray<NSString *> * array = task.result;
        NSMutableArray * result = [[NSMutableArray alloc] init];
        for (NSString * obj in array) {
            // replace with sprite 
            id sprite = [@"Rendered " stringByAppendingString:obj];
            [result addObject:sprite];
        }
        return result;

    }] continueWithExecutor:[BFExecutor mainThreadExecutor] withBlock:^id _Nullable(BFTask * _Nonnull task) {

        NSLog(@"[%@] - Update UI with all sprite objects: %@", [NSThread currentThread], task.result);
        // TODO update the UI here.
        return nil;
    }];

}

但是使用这个解决方案,所有精灵都是fetch(flattened?)然后是UI更新。如果要更新UI,每次提取精灵时,都可以执行以下操作:

- (void)queryRenderUpdateMany {

    NSThread *currentThread = [NSThread currentThread];
    NSLog(@"current thread is %@ ", currentThread);

    [[[BFTask taskFromExecutor:[Tasks backgroundExecutor] withBlock:^id _Nonnull{

        NSLog(@"[%@] - Querying model objects", [NSThread currentThread]);
        return @[@"Riri", @"Fifi", @"LouLou"];

    }] continueWithExecutor:[BFExecutor immediateExecutor] withBlock:^id _Nullable(BFTask * _Nonnull task) {

        NSArray<NSString *> * array = task.result;
        NSMutableArray * result = [[NSMutableArray alloc] init];
        for (NSString * obj in array) {

            BFTask *renderUpdate = [[BFTask taskFromExecutor:[BFExecutor immediateExecutor] withBlock:^id _Nonnull{

                NSLog(@"[%@] - Fetching sprite for %@", [NSThread currentThread], obj);
                return [@"Rendered " stringByAppendingString:obj];

            }] continueWithExecutor:[BFExecutor mainThreadExecutor] withBlock:^id _Nullable(BFTask * _Nonnull task) {

                NSLog(@"[%@] - Update UI with sprite %@", [NSThread currentThread], task.result);
                return nil;

            }];
            [result addObject: renderUpdate];
        }

        return [BFTask taskForCompletionOfAllTasks:result];

    }] continueWithExecutor:[BFExecutor mainThreadExecutor] withBlock:^id _Nullable(BFTask * _Nonnull task) {
        NSLog(@"[%@] - Updated UI for all sprites", [NSThread currentThread]);
        return nil;
    }];

}

此处,中间任务创建一个任务,一旦所有renderUpdate任务完成,该任务将完成。

希望得到这个帮助。