在NSRunLoop的Apple文档中,有一些示例代码演示了在等待标志由其他东西设置时暂停执行.
BOOL shouldKeepRunning = YES; // global NSRunLoop *theRL = [NSRunLoop currentRunLoop]; while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
我一直在使用它并且它有效,但在调查性能问题时,我将其跟踪到这段代码.我使用几乎完全相同的代码片段(只是标志的名称是不同的:)如果我NSLog
在设置标志后(在另一种方法中)放置一行,然后在while()
看似随机的后面的一行在几秒钟的两个日志语句之间等待.
在较慢或较快的机器上,延迟似乎没有差别,但在运行之间的延迟至少在几秒到10秒之间不同.
我使用以下代码解决了这个问题,但原始代码不起作用似乎不对.
NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1]; while (webViewIsLoading && [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate:loopUntil]) loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];
使用此代码时,设置标志时和while循环后的日志语句现在始终小于0.1秒.
任何想法为什么原始代码表现出这种行为?
Runloops可能是一个神奇的盒子,只是发生了一些事情.
基本上你告诉runloop去处理一些事件然后返回.如果在超时之前没有处理任何事件,则返回OR.
在0.1秒的超时时间内,您经常会超时.runloop触发,不处理任何事件并以0.1秒返回.偶尔它会有机会处理一个事件.
在您的remoteFuture超时后,runloop将等待,直到它处理一个事件.所以当它返回给你时,它刚刚处理了某种事件.
短超时值将比无限超时消耗更多的CPU,但是有充分的理由使用短超时,例如,如果要终止运行runloop的进程/线程.您可能希望runloop注意到国旗已经改变,需要尽快摆脱困境.
您可能想要使用runloop观察器,以便您可以准确地看到runloop正在做什么.
有关更多信息,请参阅此Apple文档.
好的,我向您解释了问题,这是一个可能的解决方案:
@implementation MyWindowController volatile BOOL pageStillLoading; - (void) runInBackground:(id)arg { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; // Simmulate web page loading sleep(5); // This will not wake up the runloop on main thread! pageStillLoading = NO; // Wake up the main thread from the runloop [self performSelectorOnMainThread:@selector(wakeUpMainThreadRunloop:) withObject:nil waitUntilDone:NO]; [pool release]; } - (void) wakeUpMainThreadRunloop:(id)arg { // This method is executed on main thread! // It doesn't need to do anything actually, just having it run will // make sure the main thread stops running the runloop } - (IBAction)start:(id)sender { pageStillLoading = YES; [NSThread detachNewThreadSelector:@selector(runInBackground:) toTarget:self withObject:nil]; [progress setHidden:NO]; while (pageStillLoading) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } [progress setHidden:YES]; } @end
start显示进度指示器并捕获内部runloop中的主线程.它将保持在那里,直到另一个线程宣布它已完成.要唤醒主线程,它将使其处理一个除了唤醒主线程之外没有其他目的的函数.
这只是你如何做到这一点的一种方式.在主线程上发布和处理的通知可能更可取(其他线程也可以注册),但上面的解决方案是我能想到的最简单的解决方案.顺便说一句,它不是真正的线程安全.要真正是线程安全的,每次访问布尔值都需要被来自任一线程的NSLock对象锁定(使用这样的锁也会使"volatile"过时,因为受锁定的变量根据POSIX标准是隐式volatile;但是C标准不知道锁,所以这里只有volatile才能保证这个代码能够正常工作; GCC不需要为被锁保护的变量设置volatile.
一般来说,如果你自己在一个循环中处理事件,你就是在做错了.根据我的经验,它可能会导致大量混乱的问题.
如果你想以模态方式运行 - 例如,显示进度面板 - 以模态方式运行!继续使用NSApplication方法,以模态方式运行进度表,然后在加载完成时停止模式.请参阅Apple文档,例如http://developer.apple.com/documentation/Cocoa/Conceptual/WinPanel/Concepts/UsingModalWindows.html.
如果你只是想让一个视图在你的加载期间出现,但是你不希望它是模态的(例如,你希望其他视图能够响应事件),那么你应该做一些更简单的事情.例如,你可以这样做:
- (IBAction)start:(id)sender { pageStillLoading = YES; [NSThread detachNewThreadSelector:@selector(runInBackground:) toTarget:self withObject:nil]; [progress setHidden:NO]; } - (void)wakeUpMainThreadRunloop:(id)arg { [progress setHidden:YES]; }
而且你已经完成了.无需控制运行循环!
-会
如果您希望能够设置标志变量并立即注意到运行循环,则只需-[NSRunLoop performSelector:target:argument:order:modes:
要求运行循环调用将标志设置为false的方法.这将导致您的运行循环立即旋转,要调用的方法,然后将检查标志.
在您的代码中,当前线程将检查变量是否每0.1秒更改一次.在Apple代码示例中,更改变量不会产生任何影响.runloop将运行直到它处理某个事件.如果webViewIsLoading的值已经更改,则不会自动生成任何事件,因此它将保持循环,为什么会突破它?它将留在那里,直到它得到一些其他事件来处理,然后它将突破它.这可能发生在1,3,5,10或甚至20秒.在此之前,它不会突破runloop,因此它不会注意到这个变量已经改变.Iow你引用的Apple代码是不确定的.
我想你应该重新考虑一下这个问题.由于您的变量名为webViewIsLoading,您是否等待加载网页?您是否正在使用Webkit?我怀疑你根本不需要这样的变量,也不需要你发布的任何代码.相反,您应该异步编写应用程序代码.您应该启动"网页加载过程",然后返回主循环,一旦页面加载完毕,您应该异步发布在主线程中处理的通知并运行应该尽快运行的代码加载完成.