不及格*_*-八神 4 multithreading nsoperationqueue grand-central-dispatch ios
例如,我有100次for循环.并且需要更新UIImageView,最后2个方法慢慢相同.为什么?他们之间有什么不同?
//fastest
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[btnThumb setImage:[UIImage imageWithData:data] forState:UIControlStateNormal];
[scrollView addSubview:btnThumb];
}];
//slowly
dispatch_async(dispatch_get_main_queue(), ^
{
[btnThumb setImage:[UIImage imageWithData:data] forState:UIControlStateNormal];
[scrollView addSubview:btnThumb];
});
//slowly
[btnThumb setImage:[UIImage imageWithData:data] forState:UIControlStateNormal];
[self performSelectorOnMainThread:@selector(testMethod:) withObject:[NSArray arrayWithObjects:scrollView, btnThumb, nil] waitUntilDone:NO];
-(void) testMethod:(NSArray*)objs
{
UIScrollView *scroll = [objs objectAtIndex:0];
UIButton *btn = [objs lastObject];
[scroll addSubview:btn];
}
Run Code Online (Sandbox Code Playgroud)
对于后代,为了避免对此有任何疑问,让我们考虑以下测试工具,内置在一个简单的空应用程序模板中.它使用每种机制执行1000次操作,并且还有一个运行循环观察器,因此我们可以看到我们的排队异步任务如何与主运行循环的旋转相关.它记录到控制台,但是异步,因此成本NSLog不会混淆我们的测量.它还会在排队NSOperations/ dispatch_asyncs/ performSelectors任务时故意阻塞主线程,因此排队行为也不会干扰.这是代码:
#import "NSAppDelegate.h"
dispatch_queue_t gLogQueue;
#define NSLogAsync(...) dispatch_async(gLogQueue, ^{ NSLog(__VA_ARGS__); });
@implementation NSAppDelegate
{
dispatch_group_t g;
NSUInteger numOps;
useconds_t usleepDuration;
}
static void MyCFRunLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// parameters of test
numOps = 1000;
usleepDuration = 1000;
// Set up a serial queue so we can keep the cost of calling NSLog more or less out of our test case.
gLogQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
// Group allows us to wait for one test to finish before the next one begins
g = dispatch_group_create();
// Insert code here to initialize your application
CFRunLoopObserverRef rlo = CFRunLoopObserverCreate(NULL, kCFRunLoopAllActivities, YES, 0, MyCFRunLoopObserverCallBack, NULL);
CFRunLoopAddObserver(CFRunLoopGetCurrent(), rlo, kCFRunLoopCommonModes);
CFRelease(rlo);
NSCondition* cond = [[NSCondition alloc] init];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSTimeInterval start = 0, end = 0;
// pause the main thread
dispatch_async(dispatch_get_main_queue(), ^{
[cond lock];
[cond signal];
[cond wait];
[cond unlock];
});
// wait for the main thread to be paused
[cond lock];
[cond wait];
// NSOperationQueue
for (NSUInteger i = 0; i < numOps; ++i)
{
dispatch_group_enter(g);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLogAsync(@"NSOpQ task #%@", @(i));
usleep(usleepDuration); // simulate work
dispatch_group_leave(g);
}];
}
// unpause the main thread
[cond signal];
[cond unlock];
// mark start time
start = [NSDate timeIntervalSinceReferenceDate];
// wait for it to be done
dispatch_group_wait(g, DISPATCH_TIME_FOREVER);
end = [NSDate timeIntervalSinceReferenceDate];
NSTimeInterval opQDuration = end - start;
NSLogAsync(@"NSOpQ took: %@s", @(opQDuration));
// pause the main thread
dispatch_async(dispatch_get_main_queue(), ^{
[cond lock];
[cond signal];
[cond wait];
[cond unlock];
});
// wait for the main thread to be paused
[cond lock];
[cond wait];
// Dispatch_async
for (NSUInteger i = 0; i < numOps; ++i)
{
dispatch_group_enter(g);
dispatch_async(dispatch_get_main_queue(), ^{
NSLogAsync(@"dispatch_async main thread task #%@", @(i));
usleep(usleepDuration); // simulate work
dispatch_group_leave(g);
});
}
// unpause the main thread
[cond signal];
[cond unlock];
// mark start
start = [NSDate timeIntervalSinceReferenceDate];
dispatch_group_wait(g, DISPATCH_TIME_FOREVER);
end = [NSDate timeIntervalSinceReferenceDate];
NSTimeInterval asyncDuration = end - start;
NSLogAsync(@"dispatch_async took: %@s", @(asyncDuration));
// pause the main thread
dispatch_async(dispatch_get_main_queue(), ^{
[cond lock];
[cond signal];
[cond wait];
[cond unlock];
});
// wait for the main thread to be paused
[cond lock];
[cond wait];
// performSelector:
for (NSUInteger i = 0; i < numOps; ++i)
{
dispatch_group_enter(g);
[self performSelectorOnMainThread: @selector(selectorToPerfTask:) withObject: @(i) waitUntilDone: NO];
}
// unpause the main thread
[cond signal];
[cond unlock];
// mark start
start = [NSDate timeIntervalSinceReferenceDate];
dispatch_group_wait(g, DISPATCH_TIME_FOREVER);
end = [NSDate timeIntervalSinceReferenceDate];
NSTimeInterval performDuration = end - start;
NSLogAsync(@"performSelector took: %@s", @(performDuration));
// Done.
dispatch_async(dispatch_get_main_queue(), ^{
CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), rlo, kCFRunLoopCommonModes);
NSLogAsync(@"Done. NSOperationQueue: %@s dispatch_async: %@s performSelector: %@", @(opQDuration), @(asyncDuration), @(performDuration));
});
});
}
- (void)selectorToPerfTask: (NSNumber*)task
{
NSLogAsync(@"performSelector task #%@", task);
usleep(usleepDuration); // simulate work
dispatch_group_leave(g);
}
static NSString* NSStringFromCFRunLoopActivity(CFRunLoopActivity activity)
{
NSString* foo = nil;
switch (activity) {
case kCFRunLoopEntry:
foo = @"kCFRunLoopEntry";
break;
case kCFRunLoopBeforeTimers:
foo = @"kCFRunLoopBeforeTimers";
break;
case kCFRunLoopBeforeSources:
foo = @"kCFRunLoopBeforeSources";
break;
case kCFRunLoopBeforeWaiting:
foo = @"kCFRunLoopBeforeWaiting";
break;
case kCFRunLoopAfterWaiting:
foo = @"kCFRunLoopAfterWaiting";
break;
case kCFRunLoopExit:
foo = @"kCFRunLoopExit";
break;
default:
foo = @"ERROR";
break;
}
return foo;
}
static void MyCFRunLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
NSLogAsync(@"RLO: %@", NSStringFromCFRunLoopActivity(activity));
}
@end
Run Code Online (Sandbox Code Playgroud)
在此代码的输出中,我们看到以下内容(删除了不相关/重复部分):
RLO: kCFRunLoopEntry
RLO: kCFRunLoopBeforeTimers
RLO: kCFRunLoopBeforeSources
RLO: kCFRunLoopBeforeWaiting
RLO: kCFRunLoopAfterWaiting
NSOpQ task #0
RLO: kCFRunLoopExit
RLO: kCFRunLoopEntry
RLO: kCFRunLoopBeforeTimers
RLO: kCFRunLoopBeforeSources
RLO: kCFRunLoopBeforeWaiting
RLO: kCFRunLoopAfterWaiting
NSOpQ task #1
RLO: kCFRunLoopExit
... pattern repeats ...
RLO: kCFRunLoopEntry
RLO: kCFRunLoopBeforeTimers
RLO: kCFRunLoopBeforeSources
RLO: kCFRunLoopBeforeWaiting
RLO: kCFRunLoopAfterWaiting
NSOpQ task #999
RLO: kCFRunLoopExit
NSOpQ took: 1.237247049808502s
RLO: kCFRunLoopEntry
RLO: kCFRunLoopBeforeTimers
RLO: kCFRunLoopBeforeSources
RLO: kCFRunLoopBeforeWaiting
RLO: kCFRunLoopAfterWaiting
dispatch_async main thread task #0
dispatch_async main thread task #1
... pattern repeats ...
dispatch_async main thread task #999
dispatch_async took: 1.118762016296387s
RLO: kCFRunLoopExit
RLO: kCFRunLoopEntry
RLO: kCFRunLoopBeforeTimers
RLO: kCFRunLoopBeforeSources
performSelector task #0
performSelector task #1
... pattern repeats ...
performSelector task #999
performSelector took: 1.133482992649078s
RLO: kCFRunLoopExit
RLO: kCFRunLoopEntry
RLO: kCFRunLoopBeforeTimers
RLO: kCFRunLoopBeforeSources
Done. NSOperationQueue: 1.237247049808502s dispatch_async: 1.118762016296387s performSelector: 1.133482992649078
Run Code Online (Sandbox Code Playgroud)
这告诉我们的是,NSOperation在主队列上排队的每次运行循环执行一次.(顺便说一句,这将允许为每个操作绘制视图,所以如果你像OP一样在这些任务中更新UI控件,这将允许他们绘制.)使用dispatch_async(dispatch_get_main_queue(),...)和-[performSelectorOnMainThread:...]所有排队的块/选择器被称为一个在另一个之后,没有让视图绘制或类似的东西.(如果你在排队任务时没有强行暂停主runloop,你有时可以在排队过程中看到运行循环旋转一次或两次.)
最后,结果是我预期的结果:
NSOperationQueue总是会变慢,因为旋转运行循环不是免费的.在这个测试工具中,运行循环甚至没有做任何实质性的事情,它已经慢了10%dispatch_async.如果它在做什么实质性的东西,像重绘视图,这将是很多慢.至于dispatch_asyncvs performSelectorOnMainThread:,在运行循环的一次旋转中执行所有入队项目,因此差异非常小.我希望它是消息发送开销和管理目标和参数的保留/释放performSelector....
因此,与问题的含义相反NSOperationQueue,客观上不是三种机制中最快的,而是最慢的.我怀疑在OP的情况下,NSOperationQueue 看起来更快,因为它的"第一次可见变化的时间"会更短,而for dispatch_async和performSelector所有入队的操作都将被执行,然后才会重新绘制并显示新的状态.在病态情况下,我希望这意味着只有最后一帧被看到,虽然如果你在排队时没有阻止主线程,你可以得到一些可见的帧(但你实际上会有所下降)在地面上的框架.)
无论哪种异步执行机制客观上最快,它们都是非常糟糕的动画制作方式.
| 归档时间: |
|
| 查看次数: |
1132 次 |
| 最近记录: |