当我在cocoa中使用pthread,并希望在pthread函数( setBtnState )中访问cocoa控件时,它不起作用。有什么问题?
以下是源代码:
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...");
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
感谢克里斯。在backgroud线程中为了更新控件的状态,我使用 performSelectorOnMainThread 与主UI线程进行通信。
但是当按下btnEnd时,调试器控制台会显示以下信息:
2010-09-12 23:36:29.255 PThreadTest[1888:a0f] -[AppController setBtnState]: unrecognized selector sent to instance 0x100133030
在我将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 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
答案 0 :(得分:4)
您应该只从主线程而不是后台线程与您的UI进行交互。
这不仅仅是锁定与您自己的控件的交互问题;您的控件可能会与您背后的UI中的其他对象进行交互。 (例如,您的按钮可能与您的窗口交互。)这可能导致竞争条件,死锁,无效/混合状态以及其他并发问题。
当您在后台线程上进行某些处理工作时,它需要将结果(无论是中间还是最终)传达给用户,它需要通过主线程推送该通信。 Cocoa中有一些机制可以用来做到这一点:
-performSelectorOnMainThread:withObject:waitUntilDone:
方法允许您说“在主线程上运行此其他方法”,可选择等待它完成。你应该从不传递YES
waitUntilDone:
参数,因为这是一个死锁的方法。
从Mac OS X 10.6和iOS 4.0开始,有+[NSOperationQueue mainQueue]
,它返回与主线程关联的NSOperationQueue实例:您可以在此队列上放置操作,它们将在主队列上运行线程。
如果您在后台队列上使用多个操作,并且在主线程上执行某些“完成”操作(取决于所有操作),这将非常有用。您可以使用NSOperation的依赖机制来设置它们之间的依赖关系,即使它们位于不同的队列中。
您可以为NSOperation创建子类,也可以为您的操作主体使用Objective-C块(通过+[NSBlockOperation blockOperationWithBock:]
)。
同样从Mac OS X 10.6和iOS 4.0开始,还有Grand Central Dispatch,它允许您在与主线程关联的主队列上执行阻止。它是一个比NSOperation稍微冗长的API,但代价是没有直接支持依赖关系和NSOperationQueue的其他一些功能,而是用普通的C而不是Objective-C构建。
使用任何这些机制时要记住的一件事是,不能同时与多个线程中的相同数据进行交互而没有适当的并发控制(例如锁定或使用专门的无锁数据结构和原语)。你无法逃脱“哦,我只是在阅读,所以我不需要锁定”,或者“哦,我只需要启用一个按钮,我真的不需要推动它主线。“
避免此问题的一个好方法是尽可能多地将其打包成离散单元,在后台处理这些单元,然后将结果中继到主线程。
所以你的线程代码不写得像这样:
相反,您的线程代码编写如下:
不同之处在于,后者是根据正在完成的工作单元而不是用户界面来编写的,并且在应用程序的生命周期中更易于理解和更强大(例如添加功能) - 它基本上是“MVC应用于线程。”
答案 1 :(得分:1)
你想搬家:
ptr->btnNew = btnNew;
ptr->btnEnd = btnEnd;
...如果您正在使用插座并从nib文件加载,则转到awakeFromNib
。在调用awakeFromNib
并在init
之前调用awakeFromNib
之前,无法保证解决这些商店。
我不确定您是否可以使用pthread与UI进行通信。我很确定你必须使用NSThread和通知来与主线程上的控件进行通信。