objective-c 让 NSRunLoop 等待设置标志的最佳方法?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/149646/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Best way to make NSRunLoop wait for a flag to be set?
提问by Dave Verwer
In the Apple documentation for NSRunLoopthere is sample code demonstrating suspending execution while waiting for a flag to be set by something else.
在NSRunLoop的 Apple 文档中,有示例代码演示了在等待标志被其他设置设置时暂停执行。
BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
I have been using this and it works but in investigating a performance issue I tracked it down to this piece of code. I use almost exactly the same piece of code (just the name of the flag is different :) and if I put a NSLogon the line after the flag is being set (in another method) and then a line after the while()there is a seemingly random wait between the two log statements of several seconds.
我一直在使用它并且它有效,但是在调查性能问题时,我将其追溯到这段代码。我使用几乎完全相同的一段代码(只是标志的名称不同:),如果我NSLog在设置标志后(以另一种方法)在行上放了一个,然后在while()有一个看似随机的行之后在两个日志语句之间等待几秒钟。
The delay does not seem to be different on slower or faster machines but does vary from run to run being at least a couple of seconds and up to 10 seconds.
延迟在较慢或较快的机器上似乎没有什么不同,但在每次运行之间确实有所不同,至少为几秒到 10 秒。
I have worked around this issue with the following code but it does not seem right that the original code doesn't work.
我已经使用以下代码解决了这个问题,但原始代码不起作用似乎是不对的。
NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];
while (webViewIsLoading && [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate:loopUntil])
loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];
using this code, the log statements when setting the flag and after the while loop are now consistently less than 0.1 seconds apart.
使用此代码,设置标志时和 while 循环之后的日志语句现在始终相隔小于 0.1 秒。
Anyone any ideas why the original code exhibits this behaviour?
任何人都知道为什么原始代码会表现出这种行为?
采纳答案by schwa
Runloops can be a bit of a magic box where stuff just happens.
Runloops 可以是一个神奇的盒子,事情就会发生。
Basically you're telling the runloop to go process some events and then return. OR return if it doesn't process any events before the timeout is hit.
基本上你是在告诉 runloop 去处理一些事件然后返回。如果在超时之前不处理任何事件,则返回。
With 0.1 second timeout, you're htting the timeout more often than not. The runloop fires, doesn't process any events and returns in 0.1 of second. Occasionally it'll get a chance to process an event.
使用 0.1 秒超时,您会更频繁地超时。runloop 触发,不处理任何事件并在 0.1 秒内返回。偶尔它会有机会处理一个事件。
With your distantFuture timeout, the runloop will wait foreever until it processes an event. So when it returns to you, it has just processed an event of some kind.
随着您的 remoteFuture 超时,runloop 将永远等待,直到它处理一个事件。因此,当它返回给您时,它只是处理了某种事件。
A short timeout value will consume considerably more CPU than the infinite timeout but there are good reasons for using a short timeout, for example if you want to terminate the process/thread the runloop is running in. You'll probably want the runloop to notice that a flag has changed and that it needs to bail out ASAP.
短超时值将比无限超时消耗更多的 CPU,但是使用短超时是有充分理由的,例如,如果您想终止 runloop 正在运行的进程/线程。您可能希望 runloop 注意到一个标志已经改变,它需要尽快救助。
You might want to play around with runloop observers so you can see exactly what the runloop is doing.
您可能想要玩转 runloop 观察者,以便您可以准确地看到 runloop 正在做什么。
See this Apple docfor more information.
有关更多信息,请参阅此 Apple 文档。
回答by Mecki
Okay, I explained you the problem, here's a possible solution:
好的,我向您解释了问题,这是一个可能的解决方案:
@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
startdisplays a progress indicator and captures the main thread in an internal runloop. It will stay there till the other thread announces that it is done. To wake up the main thread, it will make it process a function with no purpose other than waking the main thread up.
start显示进度指示器并捕获内部运行循环中的主线程。它会一直呆在那里,直到另一个线程宣布它已完成。为了唤醒主线程,它会处理一个除了唤醒主线程之外没有其他目的的函数。
This is just one way how you can do it. A notification being posted and processed on main thread might be preferable (also other threads could register for it), but the solution above is the simplest I can think of. BTW it is not really thread-safe. To really be thread-safe, every access to the boolean needs to be locked by a NSLock object from either thread (using such a lock also makes "volatile" obsolete, as variables protected by a lock are implicit volatile according to POSIX standard; the C standard however doesn't know about locks, so here only volatile can guarantee this code to work; GCC doesn't need volatile to be set for a variable protected by locks).
这只是您如何做到这一点的一种方式。在主线程上发布和处理通知可能更可取(其他线程也可以注册它),但上面的解决方案是我能想到的最简单的解决方案。顺便说一句,它并不是真正的线程安全的。为了真正实现线程安全,对布尔值的每次访问都需要被来自任一线程的 NSLock 对象锁定(使用这样的锁也会使“易失性”过时,因为根据 POSIX 标准,受锁保护的变量是隐式易失性的;然而,C 标准不知道锁,所以这里只有 volatile 可以保证这段代码工作;GCC 不需要为受锁保护的变量设置 volatile)。
回答by Wil Shipley
In general, if you are processing events yourself in a loop, you're Doing It Wrong. It can cause a ton of messy problems, in my experience.
通常,如果您自己在循环中处理事件,那么您就做错了。根据我的经验,它可能会导致大量混乱的问题。
If you want to run modally -- for example, showing a progress panel -- run modally! Go ahead and use the NSApplication methods, run modally for the progress sheet, then stop the modal when the load is done. See the Apple documentation, for example http://developer.apple.com/documentation/Cocoa/Conceptual/WinPanel/Concepts/UsingModalWindows.html.
如果你想以模态运行——例如,显示一个进度面板——以模态运行!继续使用 NSApplication 方法,为进度表模态运行,然后在加载完成后停止模态。请参阅 Apple 文档,例如http://developer.apple.com/documentation/Cocoa/Conceptual/WinPanel/Concepts/UsingModalWindows.html。
If you just want a view to be up for the duration of your load, but you don't want it to be modal (eg, you want other views to be able to respond to events), then you should do something much simpler. For instance, you could do this:
如果您只是希望视图在加载期间保持正常运行,但不希望它是模态的(例如,您希望其他视图能够响应事件),那么您应该做一些更简单的事情。例如,你可以这样做:
- (IBAction)start:(id)sender
{
pageStillLoading = YES;
[NSThread detachNewThreadSelector:@selector(runInBackground:) toTarget:self withObject:nil];
[progress setHidden:NO];
}
- (void)wakeUpMainThreadRunloop:(id)arg
{
[progress setHidden:YES];
}
And you're done. No need to keep control of the run loop!
你已经完成了。无需控制运行循环!
-Wil
-会
回答by Chris Hanson
If you want to be able to set your flag variable and have the run loop immediately notice, just use -[NSRunLoop performSelector:target:argument:order:modes:to ask the run loop to invoke the method that sets the flag to false. This will cause your run loop to spin immediately, the method to be invoked, and then the flag will be checked.
如果您希望能够设置标志变量并立即通知运行循环,只需使用-[NSRunLoop performSelector:target:argument:order:modes:要求运行循环调用将标志设置为 false 的方法。这将导致您的运行循环立即旋转,调用方法,然后检查标志。
回答by Mecki
At your code the current thread will check for the variable to have changed every 0.1 seconds. In the Apple code example, changing the variable will not have any effect. The runloop will run till it processes some event. If the value of webViewIsLoading has changed, no event is generated automatically, thus it will stay in the loop, why would it break out of it? It will stay there, till it gets some other event to process, then it will break out of it. This may happen in 1, 3, 5, 10 or even 20 seconds. And until that happens, it will not break out of the runloop and thus it won't notice that this variable has changed. IOW the Apple code you quoted is indeterministic. This example will only work if the value change of webViewIsLoading also creates an event that causes the runloop to wake up and this seems not to be the case (or at least not always).
在您的代码中,当前线程将每 0.1 秒检查一次变量是否发生变化。在 Apple 代码示例中,更改变量不会产生任何影响。runloop 将一直运行,直到它处理一些事件。如果webViewIsLoading的值改变了,不会自动产生事件,会一直循环下去,为什么会跳出来呢?它会一直呆在那里,直到它处理其他一些事件,然后它才会脱离它。这可能会在 1、3、5、10 甚至 20 秒内发生。在此之前,它不会跳出 runloop,因此它不会注意到此变量已更改。IOW 您引用的 Apple 代码是不确定的。
I think you should re-think the problem. Since your variable is named webViewIsLoading, do you wait for a webpage to be loaded? Are you using Webkit for that? I doubt you need such a variable at all, nor any of the code you have posted. Instead you should code your app asynchronously. You should start the "web page load process" and then go back to the main loop and as soon as the page finished loading, you should asynchronously post a notification that is processed within the main thread and runs the code that should run as soon as loading has finished.
我认为你应该重新考虑这个问题。由于您的变量名为 webViewIsLoading,您是否等待网页加载?你在使用 Webkit 吗?我怀疑您根本不需要这样的变量,也不需要您发布的任何代码。相反,您应该异步编码您的应用程序。您应该启动“网页加载过程”,然后返回主循环,一旦页面加载完成,您应该异步发布在主线程内处理的通知并运行应尽快运行的代码加载完成。
回答by Jon Shea
I've had similar issues while trying to manage NSRunLoops. The discussionfor runMode:beforeDate:on the class references page says:
我在尝试管理NSRunLoops. 在讨论了runMode:beforeDate:关于类引用页说:
If no input sources or timers are attached to the run loop, this method exits immediately; otherwise, it returns after either the first input source is processed or limitDate is reached. Manually removing all known input sources and timers from the run loop is not a guarantee that the run loop will exit. Mac OS X may install and remove additional input sources as needed to process requests targeted at the receiver's thread. Those sources could therefore prevent the run loop from exiting.
如果没有输入源或计时器附加到运行循环,则此方法立即退出;否则,它会在处理第一个输入源或达到 limitDate 后返回。从运行循环中手动删除所有已知的输入源和计时器并不能保证运行循环会退出。Mac OS X 可能会根据需要安装和删除额外的输入源,以处理针对接收者线程的请求。因此,这些源可以阻止运行循环退出。
My best guess is that an input source is attached to your NSRunLoop, perhaps by OS X itself, and that runMode:beforeDate:is blocking until that input source either has some input processed, or is removed. In your case it was taking "couple of seconds and up to 10 seconds" for this to happen, at which point runMode:beforeDate:would return with a boolean, the while()would run again, it would detect that shouldKeepRunninghas been set to NO, and the loop would terminate.
我最好的猜测是,一个输入源连接到你的NSRunLoop,也许是OS X本身,而runMode:beforeDate:阻止,直到输入源或者有一定的输入处理,或者被删除。在您的情况下,发生这种情况需要“几秒钟和最多 10 秒”,此时runMode:beforeDate:将返回一个布尔值,while()将再次运行,它会检测到shouldKeepRunning已设置为NO,并且循环将终止。
With your refinement the runMode:beforeDate:will return within 0.1 seconds, regardless of whether or not it has attached input sources or has processed any input. It's an educated guess (I'm not an expert on the run loop internals), but think your refinement is the right way to handle the situation.
通过您的改进runMode:beforeDate:,无论它是否附加了输入源或是否处理了任何输入,都将在 0.1 秒内返回。这是一个有根据的猜测(我不是运行循环内部的专家),但认为您的改进是处理这种情况的正确方法。
回答by Richard
Your second example just work around as you poll to check input of the run loop within time interval 0.1.
您的第二个示例只是在您轮询以检查时间间隔 0.1 内运行循环的输入时解决。
Occasionally I find a solution for your first example:
有时我会为您的第一个示例找到解决方案:
BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSRunLoopCommonModes beforeDate:[NSDate distantFuture]]);

