Why can't access cocoa control in a pthread?
When I use pthread in cocoa, and want to access cocoa control in pthread function(setBtnState), it doesn't work. What's the problem?
The following is the source code:
AppController.h
1 //
2 // AppController.h
3 // PThreadTest
4 //
5 // Created by zhu on 10-9-5.
6 // Copyright 2010 __MyCompanyName__. All rights reserved.
7 //
8
9 #import <Cocoa/Cocoa.h>
10
11
12 @interface AppController : NSObject {
13 IBOutlet NSButton *btnNew;
14 IBOutlet NSButton *btnEnd;
15 }
16
17 -(IBAction)newThread:(id)sender;
18 -(IBAction)endThread:(id)sender;
19
20 @end
AppController.m
1 //
2 // AppController.m
3 // PThreadTest
4 //
5 // Created by zhu on 10-9-5.
6 // Copyright 2010 __MyCompanyName__. All rights reserved.
7 //
8
9 #import "AppController.h"
10 #import <pthread.h>
11
12
13 @implementation AppController
14
15 struct mydata {
16 pthread_mutex_t mutex;
17 pthread_cond_t cond;
18 int stop;
19 NSButton *btnNew;
20 NSButton *btnEnd;
21 };
22
23 struct mydata adata;
24 struct mydata *ptr;
25
26 void setBtnState(struct mydata *p) {
27 BOOL stop = NO;
28 if (p->stop) {
29 stop = YES;
30 }
31 [p->btnNew setEnabled:stop];
32 [p->btnEnd setEnabled:!stop];
33 }
34
35 void* mythread(void* arg) {
36 NSLog(@"new thread start...")开发者_JAVA技巧;
37 ptr->stop = 0;
38 setBtnState(ptr);
39 pthread_mutex_lock(&ptr->mutex);
40 while (!ptr->stop) {
41 pthread_cond_wait(&ptr->cond, &ptr->mutex);
42 }
43 pthread_mutex_unlock(&ptr->mutex);
44 setBtnState(ptr);
45 NSLog(@"current thread end...");
46 }
47
48 -(id)init {
49 self = [super init];
50 ptr = &adata;
51 pthread_mutex_init(&ptr->mutex, NULL);
52 pthread_cond_init(&ptr->cond, NULL);
53 ptr->stop = 0;
54 ptr->btnNew = btnNew;
55 ptr->btnEnd = btnEnd;
56 return self;
57 }
58
59 -(IBAction)newThread:(id)sender {
60 pthread_t pid;
61 pthread_create(&pid, NULL, mythread, NULL);
62 }
63
64 -(IBAction)endThread:(id)sender {
65 pthread_mutex_lock(&ptr->mutex);
66 ptr->stop = 1;
67 pthread_mutex_unlock(&ptr->mutex);
68 pthread_cond_signal(&ptr->cond);
69 }
70
71 @end
Thanks to Chris. In the backgroud thread in order to update control's state I use performSelectorOnMainThread to communicate with the main UI thread.
But when btnEnd is pressed, the debugger console show the following infomation:
2010-09-12 23:36:29.255 PThreadTest[1888:a0f] -[AppController setBtnState]: unrecognized selector sent to instance 0x100133030
Why it doesn't work after I updated AppController.m as the following:
1 //
2 // AppController.m
3 // PThreadTest
4 //
5 // Created by zhu on 10-9-5.
6 // Copyright 2010 __MyCompanyName__. All rights reserved.
7 //
8
9 #import "AppController.h"
10 #import <pthread.h>
11
12
13 @implementation AppController
14
15 struct mydata {
16 pthread_mutex_t mutex;
17 pthread_cond_t cond;
18 int stop;
19 NSButton *btnNew;
20 NSButton *btnEnd;
21 id obj;
22 };
23
24 struct mydata adata;
25 struct mydata *ptr;
26
27 void* mythread(void* arg) {
28 NSLog(@"new thread start...");
29 ptr->stop = 0;
30 pthread_mutex_lock(&ptr->mutex);
31 while (!ptr->stop) {
32 pthread_cond_wait(&ptr->cond, &ptr->mutex);
33 }
34 pthread_mutex_unlock(&ptr->mutex);
35 [ptr->obj performSelectorOnMainThread:@selector(setBtnState) withObject:@"YES" waitUntilDone:NO];
36 NSLog(@"current thread end...");
37 }
38
39 -(void)setBtnState:(id)aobj {
40 BOOL stop = NO;
41 if ([aobj isEqualToString:@"YES"]) {
42 stop = YES;
43 }
44 [btnNew setEnabled:stop];
45 [btnEnd setEnabled:!stop];
46 }
47
48 -(id)init {
49 self = [super init];
50 ptr = &adata;
51 pthread_mutex_init(&ptr->mutex, NULL);
52 pthread_cond_init(&ptr->cond, NULL);
53 ptr->stop = 0;
54 ptr->obj = self;
55 // ptr->btnNew = btnNew;
56 // ptr->btnEnd = btnEnd;
57 return self;
58 }
59
60 - (void)awakeFromNib {
61 ptr->btnNew = btnNew;
62 ptr->btnEnd = btnEnd;
63 }
64
65 -(IBAction)newThread:(id)sender {
66 [self setBtnState:@"NO"];
67 pthread_t pid;
68 pthread_create(&pid, NULL, mythread, NULL);
69 }
70
71 -(IBAction)endThread:(id)sender {
72 pthread_mutex_lock(&ptr->mutex);
73 ptr->stop = 1;
74 pthread_mutex_unlock(&ptr->mutex);
75 pthread_cond_signal(&ptr->cond);
76 }
77
78 @end
79
You should only interact with your UI from the main thread, not from background threads.
This isn't just a matter of locking around interaction with your own controls; your controls may interact with other objects in the UI behind your back. (For example, your button may interact with your window.) This can lead to race conditions, deadlocks, invalid/mixed state, and other concurrency problems.
When you're doing some processing work on a background thread, and it needs to communicate results (whether intermediate or final) to the user, it will need to push that communication through the main thread. There are a few mechanisms in Cocoa with which to do this:
The
-performSelectorOnMainThread:withObject:waitUntilDone:
method lets you say "run this other method on the main thread," optionally waiting until it's done. You should almost never passYES
for thewaitUntilDone:
argument though, as that's a recipe for deadlock.Starting in Mac OS X 10.6 and iOS 4.0, there is
+[NSOperationQueue mainQueue]
, which returns an instance of NSOperationQueue associated with the main thread: You can place operations on this queue and they will run on the main thread.This is useful if you're using several operations on a background queue and you have some "finishing" operation to perform on the main thread that depends on all of them. You can just use NSOperation's dependency mechanism to set up dependencies between them even though they're on different queues.
You can either subclass NSOperation or use Objective-C blocks for the bodies of your operations (via
+[NSBlockOperation blockOperationWithBock:]
).Also starting in Mac OS X 10.6 and iOS 4.0, there is Grand Central Dispatch which lets you perform a block on a main queue associated with the main thread. It's a slightly less verbose API than NSOperation, but at the cost of not having direct support for dependencies and some of the other features of NSOperationQueue, and of being built in plain C rather than in Objective-C.
One thing to remember when using any of these mechanisms is that you must not interact with the same data from multiple threads at the same time without appropriate concurrency control (such as locking, or the use of specialized lock-free data structures and primitives). You can't get away with "Oh, I'm just reading, so I don't need to take a lock," or "Oh, I just need to enable a button, I don't really need to push that to the main thread."
A good way to avoid this issue is to do as much work as possible by batching it up into discrete units, processing those units in the background, and then relaying the results to the main thread.
So your threaded code is not written like this:
- spin off a thread to:
- lock the document
- get some data from the document
- unlock the document
- process the data
- tell the main thread whether to enable or disable the document's Foo button
Instead your threaded code is written like this:
- get some data from the document
- spin off a thread to:
- process the data
- then tell the main thread the result of the processing
- on the main thread:
- determine from the result of the processing whether to enable or disable the document's Foo button
The difference is that the latter is written in terms of the units of work being done, rather than in terms of the user interface, and will be both easier to understand and more robust in the face of your application's life (such as adding features) - it's essentially "MVC applied to threads."
You want to move:
ptr->btnNew = btnNew;
ptr->btnEnd = btnEnd;
... to awakeFromNib
if you are using outlets and loading from a nib file. There is no guarantee that the outlets will be resolved until awakeFromNib
is called and init
is called before awakeFromNib
.
I'm not certain you can use pthreads to communicate with the UI. I'm pretty sure you have to use NSThread and notifications to talk to controls on the main thread.
精彩评论