在后台绘制 SpriteKit 对象的 BFTask 正在锁定主线程
BFTask to draw SpriteKit objects in background is locking main thread
我正在使用 BFTasks 在后台执行一些 SpriteKit 绘图,但我不确定我是否正确使用它们,因为绘图正在锁定主线程。
每个对象由几个 SKSpriteNode 组成,这些节点在渲染前被展平。我希望每个人在被压平后立即呈现,即当我调用 [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 class:
- (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];
}
行class:
- (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 的概念 - 我认为它们会自动 运行 在后台线程上,但也许不是?
默认情况下,BFTasks 不 运行 在后台线程上!
如果你这样做:
BFTask * immediateTask = [BFTask taskWithResult: @"1"];
immediateTask 完成,即完成属性 为YES,立即在当前线程中。
此外,如果您这样做:
[task continueWithBlock:^id(BFTask *task) {
// some long running operation
return nil;
}];
任务完成后,块将在默认执行器中执行,默认执行器会立即在当前线程中执行块,除非调用堆栈太深,在这种情况下,它会被卸载到后台调度队列。
当前线程是调用 continueWithBlock 的线程。
因此,除非您在后台线程中调用前面的代码,否则长 运行ning 操作将阻塞当前线程。
但是,您可以使用显式执行程序将块卸载到不同的线程或队列:
BFTask * task = [BFTask taskFromExecutor:executor withBlock:^id {
id result = ...; // long computation
return result;
}];
选择正确的执行者很关键:
- executor = [BFExecutor defaultExecutor] 任务块在当前线程(执行任务创建的线程)上 运行 或者如果调用堆栈太深则卸载到后台队列。所以很难预测会发生什么;
- executor = [BFExecutor immediateExecutor] 任务的块 运行 在与前一个任务相同的线程上(参见下面的链接)。但是如果前一个任务是由默认执行程序 运行 你真的不知道它是哪个线程;
- executor = [BFExecutor mainThreadExecutor] 任务的块在主线程上 运行。这是用于在长时间 运行ning 操作后更新您的 UI 的那个。
- executor = [BFExecutor executorWithDispatchQueue:gcd_queue] 任务块在提供的 gcd 队列中 运行。创建一个带有后台队列的队列来执行长 运行ning 操作。队列的类型(串行或并发)将取决于要执行的任务及其依赖性。
根据执行者的不同,您会得到不同的行为。
BFTasks 的优点是您可以在不同的线程中链接和同步任务 运行ning。例如,要在长时间的 运行ning 后台操作后在主线程中更新 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;
}];
}
但是使用这个解决方案,所有精灵都被获取(扁平化?)然后 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 任务完成后完成。
希望对您有所帮助。
B
我正在使用 BFTasks 在后台执行一些 SpriteKit 绘图,但我不确定我是否正确使用它们,因为绘图正在锁定主线程。
每个对象由几个 SKSpriteNode 组成,这些节点在渲染前被展平。我希望每个人在被压平后立即呈现,即当我调用 [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 class:
- (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];
}
行class:
- (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 的概念 - 我认为它们会自动 运行 在后台线程上,但也许不是?
默认情况下,BFTasks 不 运行 在后台线程上!
如果你这样做:
BFTask * immediateTask = [BFTask taskWithResult: @"1"];
immediateTask 完成,即完成属性 为YES,立即在当前线程中。
此外,如果您这样做:
[task continueWithBlock:^id(BFTask *task) {
// some long running operation
return nil;
}];
任务完成后,块将在默认执行器中执行,默认执行器会立即在当前线程中执行块,除非调用堆栈太深,在这种情况下,它会被卸载到后台调度队列。 当前线程是调用 continueWithBlock 的线程。 因此,除非您在后台线程中调用前面的代码,否则长 运行ning 操作将阻塞当前线程。
但是,您可以使用显式执行程序将块卸载到不同的线程或队列:
BFTask * task = [BFTask taskFromExecutor:executor withBlock:^id {
id result = ...; // long computation
return result;
}];
选择正确的执行者很关键:
- executor = [BFExecutor defaultExecutor] 任务块在当前线程(执行任务创建的线程)上 运行 或者如果调用堆栈太深则卸载到后台队列。所以很难预测会发生什么;
- executor = [BFExecutor immediateExecutor] 任务的块 运行 在与前一个任务相同的线程上(参见下面的链接)。但是如果前一个任务是由默认执行程序 运行 你真的不知道它是哪个线程;
- executor = [BFExecutor mainThreadExecutor] 任务的块在主线程上 运行。这是用于在长时间 运行ning 操作后更新您的 UI 的那个。
- executor = [BFExecutor executorWithDispatchQueue:gcd_queue] 任务块在提供的 gcd 队列中 运行。创建一个带有后台队列的队列来执行长 运行ning 操作。队列的类型(串行或并发)将取决于要执行的任务及其依赖性。
根据执行者的不同,您会得到不同的行为。
BFTasks 的优点是您可以在不同的线程中链接和同步任务 运行ning。例如,要在长时间的 运行ning 后台操作后在主线程中更新 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;
}];
}
但是使用这个解决方案,所有精灵都被获取(扁平化?)然后 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 任务完成后完成。
希望对您有所帮助。
B