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

为什么不能在pthread中访问cocoa控件?,c,cocoa,pthreads,C,Cocoa,Pthreads,当我在cocoa中使用pthread,并希望在pthread functionsetBtnState中访问cocoa控件时,它不起作用。有什么问题吗 以下是源代码: AppController.h 1 // 2 // AppController.h 3 // PThreadTest 4 // 5 // Created by zhu on 10-9-5. 6 // Copyright 2010 __MyCompanyName__. All rights reserv

当我在cocoa中使用pthread,并希望在pthread functionsetBtnState中访问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      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  
您要移动:

ptr->btnNew = btnNew;
ptr->btnEnd = btnEnd;
。。。如果您正在使用Outlet并从nib文件加载,则从nib唤醒。在调用awakeFromNib和在awakeFromNib之前调用init之前,无法保证将解析这些出口

我不确定是否可以使用pthreads与UI通信。我非常确定您必须使用NSThread和通知来与主线程上的控件进行对话

您要移动:

ptr->btnNew = btnNew;
ptr->btnEnd = btnEnd;
。。。如果您正在使用Outlet并从nib文件加载,则从nib唤醒。在调用awakeFromNib和在awakeFromNib之前调用init之前,无法保证将解析这些出口


我不确定是否可以使用pthreads与UI通信。我非常确定您必须使用NSThread和通知来与主线程上的控件进行对话

您应该只从主线程与UI交互,而不是从后台线程

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

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

-performSelectorOnMainThread:withObject:waitUntilDone:方法允许您在主线程上运行另一个方法,可以选择等待,直到它完成。但是,对于waitUntilDone:argument,您几乎不应该通过YES,因为这会导致死锁

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

如果您在后台队列上使用多个操作,并且需要在主线程上执行一些依赖于所有这些操作的完成操作,那么这将非常有用。您只需使用NSOperation的依赖关系机制来设置它们之间的依赖关系,即使它们位于不同的队列上

您可以将NSOperation子类化,也可以通过+[NSBlockOperation blockOperationWithBock:]为操作体使用Objective-C块

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

使用这些机制时需要记住的一点是,如果没有适当的并发控制(如锁定)或使用专门的无锁数据结构和原语,则不能同时与来自多个线程的相同数据交互。你无法逃脱哦,我只是在阅读,所以我不需要带锁,或者哦,我只需要启用一个按钮,我真的不需要把它推到主线程

避免此问题的一个好方法是尽可能多地进行工作,将其分批放入离散单元,在后台处理这些单元,然后将结果转发给主线程

因此,线程化代码不是这样编写的:

将线程拆分为: 锁定文档 从文档中获取一些数据 解锁文档 处理数据 告诉主线程是启用还是禁用文档的Foo按钮 相反,线程化代码的编写方式如下:

从文档中获取一些数据 将线程拆分为: 处理数据 然后告诉主线程处理的结果 在主线程上: 根据处理结果确定是启用还是禁用文档的Foo按钮
区别在于后者是根据正在完成的工作单元编写的,而不是根据用户界面编写的,在应用程序的使用过程中,它将更容易理解,也更健壮,例如添加功能—它本质上是应用于线程的MVC。

您应该只从主线程与UI交互,而不是从后台线程

这不仅仅是锁定与自己控件的交互的问题;您的控件可能会在背后与UI中的其他对象交互。例如,按钮可能与窗口交互。这会导致比赛状态恶化 冲突、死锁、无效/混合状态和其他并发问题

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

-performSelectorOnMainThread:withObject:waitUntilDone:方法允许您在主线程上运行另一个方法,可以选择等待,直到它完成。但是,对于waitUntilDone:argument,您几乎不应该通过YES,因为这会导致死锁

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

如果您在后台队列上使用多个操作,并且需要在主线程上执行一些依赖于所有这些操作的完成操作,那么这将非常有用。您只需使用NSOperation的依赖关系机制来设置它们之间的依赖关系,即使它们位于不同的队列上

您可以将NSOperation子类化,也可以通过+[NSBlockOperation blockOperationWithBock:]为操作体使用Objective-C块

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

使用这些机制时需要记住的一点是,如果没有适当的并发控制(如锁定)或使用专门的无锁数据结构和原语,则不能同时与来自多个线程的相同数据交互。你无法逃脱哦,我只是在阅读,所以我不需要带锁,或者哦,我只需要启用一个按钮,我真的不需要把它推到主线程

避免此问题的一个好方法是尽可能多地进行工作,将其分批放入离散单元,在后台处理这些单元,然后将结果转发给主线程

因此,线程化代码不是这样编写的:

将线程拆分为: 锁定文档 从文档中获取一些数据 解锁文档 处理数据 告诉主线程是启用还是禁用文档的Foo按钮 相反,线程化代码的编写方式如下:

从文档中获取一些数据 将线程拆分为: 处理数据 然后告诉主线程处理的结果 在主线程上: 根据处理结果确定是启用还是禁用文档的Foo按钮
区别在于后者是根据正在完成的工作单元编写的,而不是根据用户界面编写的,并且在应用程序的生命周期中(例如添加功能)更容易理解和更健壮—它本质上是应用于线程的MVC。

非常感谢您,以及您对NSThread的建议。当我把它移到awakeFromNib时,它已经开始工作了!我调试它,init在awakeFromNib之前被调用,这就是重点。虽然使用-awakeFromNib是保证btnNew和btnEnd非nil的方法,但从非主线程与它们交互是个坏消息,可能仍然会有问题。非常感谢,还有您对NSThread的建议。当我把它移到awakeFromNib时,它已经开始工作了!我调试它,init在awakeFromNib之前被调用,这就是重点。虽然使用-awakeFromNib是保证btnNew和btnEnd非nil的方法,但从非主线程与它们交互是个坏消息,可能仍然会有问题。克里斯,主持人是看门人,而不是教授。此外,我们没有能力改变一个问题的公认答案。如果您认为所选答案是错误的,请按此处的案例。还有,你能坐这边吗?谢谢克里斯。这是否意味着cocoa或其控件不是线程安全的。但是,如果我想在另一个线程(而不是主UI线程)中更改控件的状态,比如在新线程中进行大量计算后启用一个特殊按钮,我如何在objc或cocoa中实现这一点?在swing中,为了做到这一点,我们可以使用SwingUtilities.invokeLaterRunnable doRun.Thread-safe这个形容词太简单了。有关于在Cocoa中从非主线程执行哪些操作是安全的,哪些操作是不安全的文档;如果某些东西没有列为安全的,你通常应该假设它是不安全的。我将更新我的答案,以包含一些关于让主线程代表后台线程执行某些操作的信息。我将更新我的答案,以包含将信息传递给主线程以便在那里完成工作的方法,以及如何在应用程序设计中充分利用这些信息。谢谢。换衣服
在多线程应用程序中,在后台线程上使用cocoa控件完全是一种愚蠢的方法。我更新了我的代码,为什么不起作用?克里斯,版主是看门人,不是教授。此外,我们没有能力改变一个问题的公认答案。如果您认为所选答案是错误的,请按此处的案例。还有,你能坐这边吗?谢谢克里斯。这是否意味着cocoa或其控件不是线程安全的。但是,如果我想在另一个线程(而不是主UI线程)中更改控件的状态,比如在新线程中进行大量计算后启用一个特殊按钮,我如何在objc或cocoa中实现这一点?在swing中,为了做到这一点,我们可以使用SwingUtilities.invokeLaterRunnable doRun.Thread-safe这个形容词太简单了。有关于在Cocoa中从非主线程执行哪些操作是安全的,哪些操作是不安全的文档;如果某些东西没有列为安全的,你通常应该假设它是不安全的。我将更新我的答案,以包含一些关于让主线程代表后台线程执行某些操作的信息。我将更新我的答案,以包含将信息传递给主线程以便在那里完成工作的方法,以及如何在应用程序设计中充分利用这些信息。谢谢。在多线程应用程序中,更改后台线程上cocoa控件的状态完全是一种愚蠢的方法。我更新了我的代码,为什么不起作用?
ptr->btnNew = btnNew;
ptr->btnEnd = btnEnd;