为什么不能在pthread中访问cocoa控件?

时间:2010-09-05 12:36:39

标签: c cocoa pthreads

当我在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  

2 个答案:

答案 0 :(得分:4)

您应该只从主线程而不是后台线程与您的UI进行交互。

这不仅仅是锁定与您自己的控件的交互问题;您的控件可能会与您背后的UI中的其他对象进行交互。 (例如,您的按钮可能与您的窗口交互。)这可能导致竞争条件,死锁,无效/混合状态以及其他并发问题。

当您在后台线程上进行某些处理工作时,它需要将结果(无论是中间还是最终)传达给用户,它需要通过主线程推送该通信。 Cocoa中有一些机制可以用来做到这一点:

  1. -performSelectorOnMainThread:withObject:waitUntilDone:方法允许您说“在主线程上运行此其他方法”,可选择等待它完成。你应该从不传递YES waitUntilDone:参数,因为这是一个死锁的方法。

  2. 从Mac OS X 10.6和iOS 4.0开始,有+[NSOperationQueue mainQueue],它返回与主线程关联的NSOperationQueue实例:您可以在此队列上放置操作,它们将在主队列上运行线程。

    如果您在后台队列上使用多个操作,并且在主线程上执行某些“完成”操作(取决于所有操作),这将非常有用。您可以使用NSOperation的依赖机制来设置它们之间的依赖关系,即使它们位于不同的队列中。

    您可以为NSOperation创建子类,也可以为您的操作主体使用Objective-C块(通过+[NSBlockOperation blockOperationWithBock:])。

  3. 同样从Mac OS X 10.6和iOS 4.0开始,还有Grand Central Dispatch,它允许您在与主线程关联的主队列上执行阻止。它是一个比NSOperation稍微冗长的API,但代价是没有直接支持依赖关系和NSOperationQueue的其他一些功能,而是用普通的C而不是Objective-C构建。

  4. 使用任何这些机制时要记住的一件事是,不能同时与多个线程中的相同数据进行交互而没有适当的并发控制(例如锁定或使用专门的无锁数据结构和原语)。你无法逃脱“哦,我只是在阅读,所以我不需要锁定”,或者“哦,我只需要启用一个按钮,我真的不需要推动它主线。“

    避免此问题的一个好方法是尽可能多地将其打包成离散单元,在后台处理这些单元,然后将结果中继到主线程。

    所以你的线程代码写得像这样:

    • 将一个主题分拆为:
      • 锁定文档
      • 从文档中获取一些数据
      • 解锁文件
      • 处理数据
      • 告诉主线程是启用还是禁用文档的Foo按钮

    相反,您的线程代码编写如下:

    • 从文档中获取一些数据
    • 将一个主题分拆为:
      • 处理数据
      • 然后告诉主线程处理结果
    • 在主线程上:
      • 根据处理结果确定是启用还是禁用文档的Foo按钮

    不同之处在于,后者是根据正在完成的工作单元而不是用户界面来编写的,并且在应用程序的生命周期中更易于理解和更强大(例如添加功能) - 它基本上是“MVC应用于线程。”

答案 1 :(得分:1)

你想搬家:

ptr->btnNew = btnNew;
ptr->btnEnd = btnEnd;

...如果您正在使用插座并从nib文件加载,则转到awakeFromNib。在调用awakeFromNib并在init之前调用awakeFromNib之前,无法保证解决这些商店。

我不确定您是否可以使用pthread与UI进行通信。我很确定你必须使用NSThread和通知来与主线程上的控件进行通信。