是的,它们有区别。主调度队列是一个串行队列。这意味着,当它正在运行已提交的任务时,它不能运行任何其他任务。即使它运行内部事件循环,这也是正确的。
-performSelectorOnMainThread:... 通过运行循环源进行操作。运行循环源可以在内部运行循环中触发,即使该内部运行循环是由之前对同一源的触发引起的。
其中一个情况是运行模态文件打开对话框。 (非沙盒,因此对话框在进程中。)我从提交给主调度队列的任务启动了模态对话框。事实证明,打开对话框的内部实现也异步地将一些工作分派到主队列。由于主调度队列被运行对话框的任务占用,直到对话框完成后才处理框架的任务。症状是对话框无法显示文件,直到某个内部超时已过期,这大约需要一分钟左右。
请注意,这不是由主线程对主队列发出的同步请求导致死锁的情况,尽管这也可能发生。使用GCD,这样的同步请求肯定会死锁。使用-performSelectorOnMainThread:...,它不会死锁,因为同步请求(waitUntilDone设置为YES)只是直接运行。
顺便说一句,您说“第一个代码是异步的”,好像在与第二个代码形成对比。由于在第二个代码中传递了NO参数以表示不等待执行结果,因此两者都是异步的。
更新:
考虑这样的代码:
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:...
提供了运行的机会。这是两种技术之间的重大区别。
如果您在-task1
中将NO
更改为YES
,这仍然可以避免死锁。那是另一个区别。那是因为当使用waitUntilDone
设置为true调用-performSelectorOnMainThread:...
时,它会检查是否在主线程上调用。如果是,则直接在那里调用选择器。就像调用-performSelector:withObject:
一样。