5

Ho cercato di capire il motivo di questo arresto per capire meglio come si comportano i blocchi. Ho una classe davvero semplice per innescare questo crash.Blocco con dispatch_block

@implementation BlockCrashTest 

- (void)doSomething 
{ 
    dispatch_queue_t queue = dispatch_queue_create("com.queue.test", DISPATCH_QUEUE_SERIAL); 

    __weak typeof(self) weakSelf = self; 

    dispatch_block_t block = ^{ 

     __strong typeof(weakSelf) strongSelf = weakSelf; 

     dispatch_group_t group = dispatch_group_create(); 

     dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC); 

     dispatch_group_enter(group); 
     [strongSelf performSomethingAsync:^{ 
      dispatch_group_leave(group); 
     }]; 

     if(dispatch_group_wait(group, time) != 0) { 
      NSLog(@"group already finished"); 
     } 
    }; 
    dispatch_async(queue, block); 
} 

- (void)performSomethingAsync:(void(^)(void))completion 
{ 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
     sleep(5); 
     completion(); 
    }); 
} 

- (void)dealloc 
{ 
    NSLog(@"released object"); 
} 

@end 

Ora, se io allocare la classe e semplicemente il metodo chiamano doSomething ad esso,

BlockCrashTest *someObject = [[BlockCrashTest alloc] init]; 
[someObject doSomething]; 

Si blocca con l'eccezione, EXC_BAD_INSTRUCTION e seguente stack tracce,

#0 0x000000011201119a in _dispatch_semaphore_dispose() 
#1 0x0000000112013076 in _dispatch_dispose() 
#2 0x0000000112026172 in -[OS_dispatch_object _xref_dispose]() 
#3 0x000000010ef4c2fd in __29-[BlockCrashTest doSomething]_block_invoke at /Users/Sandeep/Desktop/Test Block Crash/Test Block Crash/ViewController.m:35 
#4 0x0000000112005ef9 in _dispatch_call_block_and_release() 

Se modifico il metodo doSomething, tale da non utilizzare debole ma utilizza sé poi l'incidente non si verificano e metodi sembrano eseguire come previsto,

- (void)doSomething 
{ 
    dispatch_queue_t queue = dispatch_queue_create("com.queue.test", DISPATCH_QUEUE_SERIAL); 

    dispatch_block_t block = ^{ 

     dispatch_group_t group = dispatch_group_create(); 

     dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC); 

     dispatch_group_enter(group); 
     [self performSomethingAsync:^{ 
      dispatch_group_leave(group); 

     }]; 

     if(dispatch_group_wait(group, time) != 0) { 
      NSLog(@"group already finished"); 
     } 
    }; 
    dispatch_async(queue, block); 
} 

Perché si pianta, la mia comprensione è che l'utilizzo di deboli all'interno del blocco farei in modo che il metodo non venga chiamato, se l'oggetto viene rilasciato e ho pensato che debole sia più sicuro dell'utilizzo di self all'interno del blocco.

Il codice precedente funziona bene con weakSelf, se mantengo l'oggetto BlockCrashTest e chiamare il metodo ad esso.

Sarei davvero felice se qualcuno potesse spiegare il motivo dello schianto e cosa succederà esattamente con queste 3 diverse varianti di codice sopra che un crash e altri sembrano funzionare correttamente.

Nota: Questo è, in un modo o l'altro relativo al crollo elencati in filo, Objective-C crash on __destroy_helper_block_. Sono stato in grado di riprodurre le stesse identiche tracce dello stack con il mio codice sopra.

risposta

3

Un paio di osservazioni:

  1. Non si può avere un gruppo dispaccio con sbilanciato "entrare" e "lasciare" quando l'oggetto è deallocato dispatch_group_t. E come ilya ha sottolineato, a causa del tuo modello, strongSelf è nil, quindi stai entrando nel gruppo, ma senza lasciarlo.

    Un modello molto comune nella danza weakSelf e strongSelf è quello di controllare solo per vedere se era strongSelfnil o no, risolvendo lo squilibrio. Così, se strongSelf è nil, si bypassa la roba gruppo spedizione del tutto, ma se non è nil, entrambi "entrare" e "lasciare" si chiamerà:

    - (void)doSomething { 
        dispatch_queue_t queue = dispatch_queue_create("com.queue.test", DISPATCH_QUEUE_SERIAL); 
    
        typeof(self) weakSelf = self; 
    
        dispatch_async(queue, ^{ 
         typeof(self) strongSelf = weakSelf; 
    
         if (strongSelf) { 
          dispatch_group_t group = dispatch_group_create(); 
    
          dispatch_group_enter(group); 
          [strongSelf performSomethingAsync:^{ 
           dispatch_group_leave(group); 
          }]; 
    
          dispatch_group_wait(group, DISPATCH_TIME_FOREVER); 
         } 
        }); 
    } 
    

    Chiaramente, è necessario assicurarsi che performSomethingAsync metodo , per sé, sempre chiama il blocco che lascia il gruppo.

  2. L'altro modo di risolvere questo (se non si dispone di assicurazioni che tutto il "entrare" e "lasciare" sarà bilanciato), è quello di utilizzare i semafori: anche

    - (void)doSomething { 
        dispatch_queue_t queue = dispatch_queue_create("com.queue.test", DISPATCH_QUEUE_SERIAL); 
    
        typeof(self) weakSelf = self; 
    
        dispatch_async(queue, ^{ 
         typeof(self) strongSelf = weakSelf; 
    
         dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); 
    
         [strongSelf performSomethingAsync:^{ 
          dispatch_semaphore_signal(semaphore); 
         }]; 
    
         dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC); 
    
         if (dispatch_semaphore_wait(semaphore, time) != 0) { 
          NSLog(@"semaphore not received in time"); 
         } 
        }); 
    } 
    

    Francamente, quando si utilizzano i semafori, come sopra ho ancora credo che uno vorrebbe controllare per confermare che strongSelf non era nil. La programmazione simultanea è abbastanza confusa senza aggiungere la possibilità del messaggio a un oggetto nil risultante in un no-op.

    - (void)doSomething { 
        dispatch_queue_t queue = dispatch_queue_create("com.queue.test", DISPATCH_QUEUE_SERIAL); 
    
        typeof(self) weakSelf = self; 
    
        dispatch_async(queue, ^{ 
         typeof(self) strongSelf = weakSelf; 
    
         if (strongSelf) { 
          dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); 
    
          [strongSelf performSomethingAsync:^{ 
           dispatch_semaphore_signal(semaphore); 
          }]; 
    
          dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC); 
    
          if (dispatch_semaphore_wait(semaphore, time) != 0) { 
           NSLog(@"semaphore not received in time"); 
          } 
         } 
        }); 
    } 
    
+0

Potete anche darmi un indizio sul motivo per cui non si blocca, se uso self all'interno del blocco? È solo perché il blocco conserva se stesso? – Sandeep

+1

@GeneratorOfOne - Poiché la presenza di 'self' stabilisce un riferimento forte all'interno del blocco, che non viene risolto fino all'esecuzione del blocco. Quindi 'weakSelf' non è' nil' e 'strongSelf' non è' nil' neanche. Così, il blocco del gestore di completamento in 'performSomethingAsync' viene chiamato, assicurando che" enter "sia ora bilanciato da un" leave ". Ma se volevi che il blocco conservasse l'oggetto, ovviamente non avresti attraversato la danza Debole/forte. – Rob

+0

Questa è una buona spiegazione. Quindi, posso avere qualche oggetto autorelued che può ancora fare qualche lunga chiamata asincrona e rimanere in giro se usassi me stesso, invece di usare debole/forte. Mentre se l'oggetto è se mantenuto da solo, allora ha senso avere debole/forte, è quel senso di questa cosa? – Sandeep

2

Otterrai lo stesso crash anche se rimuovi la chiamata di self performSomethingAsync:. Questo arresto anomalo causato dall'API del semaforo libdispatch. Si può vedere traccia di assemblaggio della funzione si è schiantato _dispatch_semaphore_dispose in Xcode: enter image description here

Se cerchiamo di capire che cosa accade in questo codice vedremo che si esplicitamente marchio corrente blocco inserito il gruppo chiamando dispatch_group_enter. Quindi performSomethingAsync non chiama perché strongSelf == nil. Il che significa che dispatch_group_enter non è bilanciato con dispatch_group_leave questo perché il gruppo non può disporre correttamente e si blocca (vedi l'elenco di ASM).

Se si utilizza questo codice self anche caduto perché dispatch_group_leave(group); chiamato dalla diversa filo con dispatch_group_enter Che è anche causare lo stesso incidente, ma in un altro punto di vista: non chiamate in bilanciato nello stesso thread. chiamato blocco di completamento in coda diversa non nel tuo "com.queue.test".

Questo esempio è solo l'uso errato delle API dispatch_groups.Per vedere come usarlo correttamente vedi apple doc.

1

MacOS 10.8 e iOS 6.0 ha introdotto ARC per spedire oggetti. Dalla documentazione GCD Objects and Automatic Reference Counting:

Quando si crea l'app utilizzando il compilatore Objective-C, tutti gli oggetti di invio sono oggetti Objective-C. Pertanto, quando è attivo il conteggio dei riferimenti automatico (ARC), gli oggetti di invio vengono mantenuti e rilasciati automaticamente come qualsiasi altro oggetto Objective-C. Quando ARC non è abilitato, utilizzare le funzioni dispatch_retain e dispatch_release (o semantica Objective-C) per conservare e rilasciare gli oggetti di invio. Non è possibile utilizzare le funzioni di conservazione/rilascio di Core Foundation.

Se è necessario utilizzare la semantica di conservazione/rilascio in un'app abilitata per ARC con una destinazione di distribuzione successiva (per mantenere la compatibilità con il codice esistente), è possibile disabilitare gli oggetti di dispacciamento basati su Objective-C aggiungendo -DOS_OBJECT_USE_OBJC = 0 a le tue bandiere del compilatore.

Nel tuo caso, ARC gestisce felicemente il ciclo di vita del tuo dispatch_group_t. E, sfortunatamente, il tuo codice sta causando il rilascio del gruppo mentre il blocco è ancora in attesa. Quando il gruppo è scaduto, viene rilasciato, quindi quando si chiama dispatch_group_leave si blocca, poiché il gruppo è già stato rilasciato.

Suggerirei per lo meno di controllare se il gruppo è NULL prima di provare a lasciarlo.

Inoltre, la logica del risultato di attesa è invertita. Un risultato pari a zero indica che il gruppo è stato svuotato prima del timeout, un risultato diverso da zero indica che è stato raggiunto il timeout.