我读了关于SO的评论,将队列调度到主线程与在主线程上执行代码不同。如果我理解正确,用户就是说这个
dispatch_async(dispatch_get_main_queue(),
^{
// some code
});
与此
不一样[self performSelectorOnMainThread:@selector(doStuff)
withObject:nil waitUntilDone:NO];
- (void) doStuff {
// some code
}
这个评论有一些真实的吗?
排除第一个代码是异步的事实,对我来说,两个代码在主线程上都是相同的。它们之间有任何技术差异吗?
我问的是因为我有一些代码在主线程上使用dispatch_async更新UI并且它无法正常工作但是当我使用performSelectorOnMainThread将其更改为第二个表单时,它可以工作。
答案 0 :(得分:17)
是的,有区别。主调度队列是一个串行队列。这意味着,虽然它正在运行已提交给它的任务,但它无法运行任何其他任务。即使它运行内部事件循环也是如此。
-performSelectorOnMainThread:...
通过运行循环源运行。运行循环源可以在内部循环循环中触发,即使该内部循环循环是先前触发该相同源的结果。
这个位置的一个例子是运行模态文件打开对话框。 (非沙盒,因此对话框正在进行中。)我从提交到主调度队列的任务启动了模式对话框。事实证明,打开对话框的内部实现也会异步地将一些工作分配给主队列。由于主调度队列被运行对话框的任务占用,因此在对话框完成之前,它不会处理框架的任务。症状是,在某个内部超时到期之前,对话框将无法显示文件,大约一分钟左右。
请注意,这不是由主线程对主队列的同步请求引起的死锁情况,尽管这也可能发生。使用GCD,这样的同步请求肯定会死锁。对于-performSelectorOnMainThread:...
,它不会,因为同步请求(waitUntilDone
设置为YES
)只是直接运行。
顺便说一句,你说“第一个代码是异步的”,好像要与第二个代码形成对比。两者都是异步的,因为您在第二个NO
中传递了waitUntilDone
。
更新
考虑这样的代码:
dispatch_async(dispatch_get_main_queue(), ^{
printf("outer task, milestone 1\n");
dispatch_async(dispatch_get_main_queue(), ^{
printf("inner task\n");
});
// Although running the run loop directly like this is uncommon, this simulates what
// happens if you do something like run a modal dialog or call -[NSTask waitUntilExit].
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
printf("outer task, milestone 2\n");
});
这将记录:
outer task, milestone 1
outer task, milestone 2
inner task
在外部任务完成之前,内部任务不会运行。即使外部任务运行主运行循环也是如此,这是处理分派到主队列的任务的过程。原因是主队列是一个串行队列,在它仍在运行任务时永远不会启动新任务。
如果您将内部dispatch_async()
更改为dispatch_sync()
,那么程序将会死锁。
相比之下,请考虑:
- (void) task2
{
printf("task2\n");
}
- (void) task1
{
printf("task1 milestone 1\n");
[self performSelectorOnMainThread:@selector(task2) withObject:nil waitUntilDone:NO];
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
printf("task1 milestone 2\n");
}
(... in some other method:)
[self performSelectorOnMainThread:@selector(task1) withObject:nil waitUntilDone:NO];
那将记录:
task1 milestone 1
task2
task1 milestone 2
在-task1
内运行运行循环,为内部-performSelectorOnMainThread:...
提供了运行的机会。这是两种技术之间的巨大差异。
如果您将NO
更改为YES
中的-task1
,这仍然可以解决问题。这是另一个不同之处。那是因为,当-performSelectorOnMainThread:...
被设置为true时调用waitUntilDone
时,它会检查它是否在主线程上被调用。如果是,那么它就直接在那里调用选择器。就好像这只是对-performSelector:withObject:
的调用。
答案 1 :(得分:6)
是的,似乎有一点不同。让我们编写一些代码并查看它是什么:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
NSLog(@"Starting!");
CFRunLoopObserverRef o1 = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
NSLog(@"runloop phase: %@", NSStringFromRunLoopActivity(activity));
});
CFRunLoopAddObserver(CFRunLoopGetMain(), o1, kCFRunLoopDefaultMode);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"dispatch_async 1");
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"dispatch_async 2");
});
[self performSelectorOnMainThread:@selector(log) withObject:nil waitUntilDone:NO];
[self performSelectorOnMainThread:@selector(log) withObject:nil waitUntilDone:NO];
/*
NSLog(@"Reentering");
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
NSLog(@"Reexiting");
*/
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
CFRunLoopRemoveObserver(CFRunLoopGetMain(), o1, kCFRunLoopDefaultMode);
CFRelease(o1);
});
}
- (void)log {
NSLog(@"performSelector");
}
在这段代码中,我们将设置RunLoop Observer以在runloop旋转时注入一些日志记录。这将有助于我们了解异步代码何时执行。 (NSStringFromRunLoopActivity()
函数是一个自定义函数,只需将activity
值转换为字符串;其实现无趣)
我们将向主队列发送两个东西,我们将向主线程发送两个log
选择器。请注意,我们在dispatch_async
来电之前-performSelector:
。
然后我们要对观察者进行一些拆解,这样我们就不会发现日志了。
当我们运行时,我们看到:
2014-05-25 07:57:26.054 EmptyAppKit[35437:303] Starting!
2014-05-25 07:57:26.055 EmptyAppKit[35437:303] runloop phase: Entry
2014-05-25 07:57:26.055 EmptyAppKit[35437:303] runloop phase: BeforeTimers
2014-05-25 07:57:26.055 EmptyAppKit[35437:303] runloop phase: BeforeSources
2014-05-25 07:57:26.056 EmptyAppKit[35437:303] performSelector
2014-05-25 07:57:26.056 EmptyAppKit[35437:303] performSelector
2014-05-25 07:57:26.056 EmptyAppKit[35437:303] runloop phase: Exit
2014-05-25 07:57:26.056 EmptyAppKit[35437:303] runloop phase: Entry
2014-05-25 07:57:26.056 EmptyAppKit[35437:303] runloop phase: BeforeTimers
2014-05-25 07:57:26.056 EmptyAppKit[35437:303] runloop phase: BeforeSources
2014-05-25 07:57:26.056 EmptyAppKit[35437:303] dispatch_async 1
2014-05-25 07:57:26.056 EmptyAppKit[35437:303] dispatch_async 2
2014-05-25 07:57:26.057 EmptyAppKit[35437:303] runloop phase: Exit
2014-05-25 07:57:26.057 EmptyAppKit[35437:303] runloop phase: Entry
2014-05-25 07:57:26.057 EmptyAppKit[35437:303] runloop phase: BeforeTimers
2014-05-25 07:57:26.057 EmptyAppKit[35437:303] runloop phase: BeforeSources
2014-05-25 07:57:26.067 EmptyAppKit[35437:303] runloop phase: BeforeWaiting
2014-05-25 07:57:26.068 EmptyAppKit[35437:303] runloop phase: AfterWaiting
2014-05-25 07:57:26.068 EmptyAppKit[35437:303] runloop phase: Exit
...
从这里我看到了几件事:
performSelector
和dispatch_async
两种情况下,运行循环都会检查源,但永远不会像之后那样进入“BeforeWaiting”阶段。performSelectors
和dispatch_asyncs
。 [[NSRunLoop mainRunLoop] runUntilDate...]
代码,则事物的顺序不会改变,并且会重新执行块和选择器重新进入运行循环。