NSOperation blocks UI painting?
I'm after some advice on the use of NSOperation
and drawing:
I have a main thread create my NSOperation
subclass, which then adds it to an NSOperationQueue
.
My NSOperation
does some heavy processing, it is intended to loop in its main() method for several minutes, constantly processing some work, but for now I just have a while() loop with a sleep(1) inside, which is set to go around just 5 times (for testing).
The main (original) thread which spawns this NSOperation
is responsible for drawing to a view and updating the UI.
I intended to have the NSOperation thread use a notification to tell the main thread that it had done some amount of processing, at the moment this notification is sent once each time it goes through its while() loop (that is; once a second because it's just doing sleep(1)). The main thread (the view), registers to receive these notifications.
The notifications get through to the main thread immediately, and seeming asynchronously, looking just fine. It appears that both threads run as expected... that is - concurrently. (I use NSLog() just to check roughly when each thread sends and receives the notification).
When the view receives a notification, and its handler met开发者_如何学运维hod is called, I simply increment an integer variable, and try and draw this to the view (as a string of course). In testing, the code in drawRect: draws this integer (as a string) to the screen just fine.
However: here's my problem (sorry it's taken a little while to get here): when the main thread (view) receives a notification from the NSOperation, it updates this test integer and calls [self setNeedsDisplay]. However, the view does not redraw itself until the NSOperation is finished! I expected that the NSOperation, being a separate thread, would have no ability to block the main thread's event loop, but it appears that this is what is happening. When the NSOperation finishes and its main() returns, the view finally redraws itself straight away.
Perhaps I am not using NSOperation
correctly. I am using it in "non- concurrent" mode, but despite the name my understanding is that this still produces a new thread and allows for asynchronous processing.
Any help or advice much appreciated, if you'd like to see some code just let me know.
The method in the observer that is performed in response to your notification is not being performed on the main thread.
So, in that method, you can force another method to run on the main thread using performSelectorOnMainThread:withObject:waitUntilDone:
.
For example:
MyOperation.m
- (void)main {
for (int i = 1; i <= 5; i++) {
sleep(1);
[[NSNotificationCenter defaultCenter] postNotificationName:@"GTCNotification" object:[NSNumber numberWithInteger:i]];
}
}
MyViewController.m
- (void)setupOperation {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(myNotificationResponse:) name:@"GTCNotification" object:nil];
NSOperationQueue *opQueue = [[NSOperationQueue alloc] init];
MyOperation *myOp = [[MyOperation alloc] init];
[opQueue addOperation:myOp];
[myOp release];
[opQueue release];
}
- (void)myNotificationResponse:(NSNotification*)note {
NSNumber *count = [note object];
[self performSelectorOnMainThread:@selector(updateView:) withObject:count waitUntilDone:YES];
}
- (void)updateView:(NSNumber*)count {
countLabel.text = count.stringValue;
}
Most people know that any display-related task must be done on the main thread. However, as a direct consequence of that, any change to a Cocoa bindings property that may affect the drawing must also be modified on the main thread (because the KVO triggers will be handled on the thread from which they are triggered). This is a surprise to most people: in particular, it is not safe to call [self setNeedsDisplay] from a thread other than the main thread.
As mentioned by gerry, your NSNotification is not processed on the main thread, it is processed on the thread from which it is sent. Hence in your NSNotification handler, you must send your commands back to the main thread if they are display-related or if they affect Cocoa bindings. @performSelector works, but with queues, I have found an easier and more readable method:
[ [ NSOperationQueue mainQueue] addOperationWithBlock:^(void) {
/* Your main-thread code here */ }];
It avoids the definition of a secondary helper function whose only purpose is to be called from the main thread. mainQueue was defined only in >10.6.
精彩评论