NSPostWhenIdle not doing its job?
NSPostWhenIdle not doing its job?
- Subject: NSPostWhenIdle not doing its job?
- From: Jonathan Taylor <email@hidden>
- Date: Sat, 14 May 2011 17:46:53 +0100
Hi folks,
This is inevitably a bit of a vague question, but I am hoping somebody may have some words of wisdom to offer nonetheless. The problem can be summarised as: in spite of having a backlog of notifications building up on the main thread, NSPostWhenIdle-qualified notifications do seem to be making it through. This is causing me problems because this idle work is swallowing up main thread time, making the backlog even worse! I am trying to understand why this is happening and whether I can do anything to alleviate the situation.
My code receives and processes frame data from a video camera driver. The sequence of events is as follows. It may seem a bit complicated, but I am including all the levels of indirection in case that proves to be relevant, rather than simplifying things too much.
- From the driver callback I post a block (function A) onto a custom GCD queue(this extra level of indirection is important because I cannot do *anything* in the driver callback that may cause it to take more than the bare minimum of time - and that includes allocating any memory, even!).
- Function A may do a number of things, but in this case all it does is posts two notifications to the application main thread (this is necessary for subscribers that may modify the GUI etc). Notification 1 is queued as normal, and Notification 2 is posted with the NSPostWhenIdle qualifier
- Function B subscribes to notification 1. Again, it may do a number of things but in this case all it does here is request that the data be saved to disk (the actual saving is done in a separate thread to avoid holding up the main thread).
- Function C subscribes to notification 2 (which, to emphasize again, should be being coalesced and posted when the main thread is idle). Function C updates the GUI to reflect the frame image most recently processed [by function B]
My intention with this is that this should prioritize the "important" bits of the code, but that if there are still spare CPU cycles available then this time is used by Function C to draw in the GUI.
The actual code used to post a coalesced notification (NSPostWhenIdle) is as follows:
void QueueNotificationOnMainThreadWithCoalescing(const NSString *notificationName, NSPostingStyle style, id obj)
{
NSNotification *myNotification = [NSNotification notificationWithName:notificationName object:obj];
[[NSOperationQueue mainQueue] addOperationWithBlock:
^{
[[NSNotificationQueue defaultQueue]
enqueueNotification:myNotification
postingStyle:style
coalesceMask:NSNotificationCoalescingOnName|NSNotificationCoalescingOnSender
forModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
}];
}
If Function B does nothing (i.e. does not write to disk), then as CPU load increases the image as viewed in the GUI becomes jerky or stops updating altogether, indicating that PostWhenIdle is doing its throttling job correctly.
However if Function B does cause the data to be saved to disk then things do not work as expected, even in the absence of any non-essential CPU load. A backlog of Notification 1 events builds up on the main thread - i.e. the main thread is not managing to keep up with the rate at which frames are coming in and notifications are being posted. In spite of this, Function C *is* being called at regular intervals and the GUI is kept updated and looks smooth. There is however the outward symptom that the image shown in the GUI does not reflect the actual real-world reality because all it can show is the most recent frame that has actually been processed (and there is a backlog) - in other words if the camera is pointed at a clock then the image of the clock in the GUI will apparently be running "slow" compared to the real world clock.
For what it's worth, on the core 2 duo macbook where I see the problems the CPU load reported by Shark is:
start 39.0% [the main thread]
unix_syscall 27.7% [which appears to be associated with saving to disk]
user_trap 8.4% [looks like mostly VM-related]
start_wqthread 8.2% [various functions of mine, including function A and the one that does the actual saving to disk]
thread_start 6.8% [the camera driver library]
Shark system trace shows plenty going on both on the main thread and on numerous other threads of mine. However the main thread spends the vast majority of the time blocked on [NSWindow displayIfNeeded]! This is what is causing the massive bottleneck on the main thread. Because there is a backlog on the main thread we should NOT be getting postWhenIdle notifications, but because we do we are doing the drawing, which is what is holding the main thread up in the first place! Somehow, the NSPostWhenIdle qualifier (plus coalescing) is still letting my gui update notifications through in spite of a backlog of other more urgent notifications building up on the main thread.
Unless there are some subtle gotchas with NSPostWhenIdle I think the behaviour I'm seeing indicates a problem with the OS scheduling (though I hope I can find a way of working around it!). I can do no more than wildly speculate about what is causing the problem though. Can anybody suggest how I might be able to delve deeper into this and/or work around it?
Thanks very much to anybody who has had the patience to read this far!
Jonny_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden