Почему нельзя получить контроль какао в pthread? - PullRequest
1 голос
/ 05 сентября 2010

Когда я использую pthread в какао и хочу получить доступ к элементу управления какао в функции pthread ( setBtnState ), он не работает. В чем проблема?

Ниже приведен исходный код:

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, чтобы обновить состояние элемента управления, я использую executeSelectorOnMainThread для связи с основным потоком пользовательского интерфейса.

Но при нажатии 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 ]

4 голосов
/ 08 сентября 2010

Вы должны взаимодействовать с пользовательским интерфейсом только из основного потока, а не из фоновых потоков.

Это не просто вопрос взаимодействия с вашими собственными средствами управления; ваши элементы управления могут взаимодействовать с другими объектами в пользовательском интерфейсе за вашей спиной. (Например, ваша кнопка может взаимодействовать с вашим окном.) Это может привести к условиям гонки, взаимоблокировкам, неверному / смешанному состоянию и другим проблемам параллелизма.

Когда вы выполняете некоторую обработку в фоновом потоке, и ему необходимо сообщить результаты (промежуточные или конечные) пользователю, ему нужно будет передать это сообщение через основной поток. В Какао есть несколько механизмов, с помощью которых это можно сделать:

  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, которая позволяет выполнять блок в основной очереди, связанной с основным потоком. Это немного менее многословный API, чем NSOperation, но за счет отсутствия прямой поддержки зависимостей и некоторых других функций NSOperationQueue и построения в простом C, а не в Objective-C.

При использовании любого из этих механизмов следует помнить, что вы не должны одновременно взаимодействовать с одними и теми же данными из нескольких потоков без соответствующего контроля параллелизма (такого как блокировка или использование специализированных структуры данных и примитивы без блокировок). Вы не можете сойти с рук «О, я просто читаю, поэтому мне не нужно снимать блокировку» или «О, мне просто нужно включить кнопку, мне не нужно нажимать это, чтобы основная нить. "

Хороший способ избежать этой проблемы - выполнить как можно больше работы, разбив ее на отдельные единицы, обрабатывая эти единицы в фоновом режиме и передавая результаты в основной поток.

Итак, ваш многопоточный код не написан так:

  • раскрутить нить, чтобы:
    • заблокировать документ
    • получить некоторые данные из документа
    • разблокировать документ
    • обработка данных
    • сообщить основной ветке, включать или отключать кнопку Foo документа

Вместо этого ваш многопоточный код записывается так:

  • получить некоторые данные из документа
  • раскрутить нить, чтобы:
    • обработка данных
    • затем сообщите основному потоку результат обработки
  • в основной теме:
    • определить по результатам обработки, включить или отключить кнопку Foo документа

Разница заключается в том, что последний написан с точки зрения выполняемых единиц работы, а не с точки зрения пользовательского интерфейса, и будет более легким для понимания и более надежным перед лицом жизни вашего приложения (например, добавление функций) - по сути, «MVC применяется к потокам».

1 голос
/ 05 сентября 2010

Вы хотите переехать:

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

... до awakeFromNib, если вы используете розетки и загружаете из файла пера. Нет гарантии, что выходы будут разрешены до тех пор, пока не будет вызван awakeFromNib и не будет вызван init до awakeFromNib.

Я не уверен, что вы можете использовать pthreads для связи с пользовательским интерфейсом. Я уверен, что вы должны использовать NSThread и уведомления, чтобы общаться с элементами управления в главном потоке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...