2015-08-06 2 views
1

У меня есть два метода, которые запускаются в последовательной очереди. Каждый метод возвращает копию некоторого класса. Я пытаюсь достичь решения безопасности потока, а также для обеспечения целостности данных.Последовательная очередь GCD и состояние гонки

, например:

-(Users *) getAllUsers 
{ 
     __block copiedUsers; 
     dispatch_sync(_backgroundQueue, ^{ 
      copiedUsers = [self.users copy]; // return copy object to calling thread. 
     }); 
     return copiedUsers; 
} 

-(Orders *) getAllOrders 
{ 
     __block copiedOrders; 
     dispatch_sync(_backgroundQueue, ^{ 
      copiedOrders = [self.Orders copy]; // return copy object to calling thread. 
     }); 
     return copiedOrders; 
} 

В дополнение к этим двум методам, у меня есть класс, который работник добавлять/удалять пользователей и заказы, все сделано через последовательный очереди backgroundQueue.

Если в главном потоке я вызываю getAllUsers, а затем getAllOrders сразу после другого, моя целостность данных небезопасна, потому что между двумя вызовами рабочий класс мог бы изменить модель.

Мой вопрос: как я могу сделать вызывающему приятный интерфейс, который позволяет нескольким методам работать атомарно?

ответ

2

Модель обновляется только из backgroundQueue последовательной очереди. Клиент обсуждает модель с помощью метода, который получает блок, который запускается в фоновом режиме.

Кроме того, чтобы не замораживать основной поток, я создаю очередную очередь и запускаю блок, который говорит со способом шлюза.

P.S - внимание, что dispatch_sync называется только в runBlockAndGetNeededDataSafely, чтобы избежать взаимоблокировок.

образец

Код:

aViewController.m

ManagerClass *m = [ManagerClass new];  
dispatch_queue_t q = dispatch_queue_create("funnelQueue", DISPATCH_QUEUE_SERIAL); 
dispatch_block_t block_q = ^{ 
    __Users *users; 
    __Orders *orders; 
    [manager runBlockAndGetNeededDataSafely:^ 
    { 
      users = [manager getUsers]; 
      orders = [manager getOrders]; 
      dispatch_async(dispatch_get_main_queue(), 
      ^{ 
       // got data safely - no thread issues, copied objects. update UI! 
       [self refreshViewWithUsers:users 
            orders:orders]; 
      }); 
    }]; 
} 
dispatch_async(q, block_q); 

Manager.m реализация:

-(void) runBlockInBackground:(dispatch_block_t) block 
{ 
    dispatch_sync(self.backgroundQueue, block); 
} 
-(Users *) getAllUsers 
{ 
     return [self.users copy]; 
} 

-(Orders *) getAllOrders 
{ 
     return [self.Orders copy]; 
} 
+0

Я отредактировал ваш ответ для более легкого обсуждения. Код внутри 'block_q' ставит очередь' task' в 'backgroundQueue', а затем уведомляет основной поток. Вы получите сигнал обновления перед обновлением 'orders' и' users'. Я думаю, что ваш код будет работать правильно, если у вас есть 'dispatch_semaphore_t' в' block_q' и дождитесь завершения 'runBlockAndGetNeededDataSafely'. – sahara108

+0

Спасибо за редактирование, я его одобрил. Какой сигнал обновления вы имеете в виду? И зачем мне нужно dispatch_semaphore_t? – kernix

+0

Сигнал обновления - 'refreshViewWithUsers'. Поскольку он будет выполнять 'refreshViewWithUsers' независимо от того, что закончилось' runBlockAndGetNeededDataSafely' или нет – sahara108

0

Ваш пример не работает. Я предлагаю использовать обратный вызов завершения. У вас должна быть возможность узнать, когда работник закончит свою работу, чтобы вернуться к значению.

- (void)waitForCompletion:(BOOL*)conditions length:(int)len timeOut:(NSInteger)timeoutSecs { 
    NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeoutSecs]; 
    BOOL done = YES; 
    for (int i = 0; i < len; i++) { 
     done = done & *(conditions+i); 
    } 
    do { 
     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeoutDate]; 
     if([timeoutDate timeIntervalSinceNow] < 0.0) 
      break; 

     //update done 
     done = YES; 
     for (int i = 0; i < len; i++) { 
      done = done & *(conditions+i); 
     } 
    } while (!done); 
} 


-(void) getAllUsers:(void(^)(User* user, NSError* error))completion 
{ 
    dispatch_async(_backgroundQueue, ^{ 
     BOOL condition[2] = [self.userCondition, self.orderCondition]; 
     [self waitForCompletion: &condition[0] length:2 timeOut:60]; 
     if (completion) { 
      completion([self.users copy], nil); 
     } 
    }); 
} 
+0

спасибо, обновленный вопрос с правильной реализации. – kernix

+0

Ваш пример вернет указатель и изменит его значение позже. Вы не делаете, когда это происходит, так что это сложно, если вам нужно обновить интерфейс. – sahara108

+0

Да, лучше, чтобы методы запуска выполнялись синхронно, так как я обновил свой вопрос. – kernix

1

Чтобы ответить на ваш вопрос о том, как проверить текущую очередь: Во-первых, при создании в очереди, поставьте метку:

static void* queueTag = &queueTag; 
dispatch_queue_t queue = dispatch_queue_create("a queue", 0); 
dispatch_queue_set_specific(queue, queueTag, queueTag, NULL); 

, а затем запустить блок, как это:

-(void)runBlock:(void(^)()) block 
{ 
    if (dispatch_get_specific(queueTag) != NULL) { 
     block(); 
    }else { 
     dispatch_async(self.queue, block); 
    } 
} 
+0

Уход. Вы уверены, что нет проблем с этим? Потому что это действительно потрясающий способ предотвратить взаимоблокировки :-) – kernix

+0

конечно. Я использую его и в своем проекте. – sahara108

+0

Это мило. Я тоже начну использовать его, это может быть очень полезно. Благодаря! – kernix

Смежные вопросы