2011-10-19 8 views
36

Stavo riscontrando qualche problema nell'unità di test di un grande codice di invio centrale con il framework di test delle unità Xcode incorporato, SenTestingKit. Sono riuscito a far bollire il mio problema. Ho un test unitario che costruisce un blocco e tenta di eseguirlo sul thread principale. Tuttavia, il blocco non viene mai effettivamente eseguito, quindi il test si blocca perché è un invio sincrono.dispatch_sync sulla coda principale si blocca nel test dell'unità

- (void)testSample { 

    dispatch_sync(dispatch_get_main_queue(), ^(void) { 
     NSLog(@"on main thread!"); 
    }); 

    STFail(@"FAIL!"); 
} 

Che cos'è l'ambiente di testing che causa questo blocco?

+3

Buona domanda e attendo la risposta corretta. Ho trovato più volte che usare dispatch_sync sulla coda principale finisce in deadlock quindi lo evito in generale. –

+0

@ D.C. più volte o SEMPRE? Sono curioso come in come puoi 'dispatch_sync (dispatch_get_main_queue()' ** while ** sul thread principale non creerà dead lock !? – Honey

risposta

99

dispatch_sync esegue un blocco su una determinata coda e attende che si completi. In questo caso, la coda è la coda di invio principale. La coda principale esegue tutte le sue operazioni sul thread principale, nell'ordine FIFO (first-in-first-out). Ciò significa che ogni volta che si chiama dispatch_sync, il nuovo blocco verrà inserito alla fine della riga e non verrà eseguito fino a quando non viene eseguito tutto il resto prima della coda.

Il problema è che il blocco appena accodato è alla fine della fila in attesa di eseguire sul principale filo mentre il metodo testSample è attualmente in esecuzione sul thread principale. Il blocco alla fine della coda non può accedere al thread principale finché il metodo corrente (stesso) non termina con utilizzando il thread principale. Tuttavia, dispatch_sync significa Invia un oggetto blocco per l'esecuzione su una coda di invio e attende fino al termine del blocco.

+1

Eccellente, grazie per la spiegazione approfondita. – Drewsmits

+1

Lovely. I second the thanks Ho il codice che voglio forzare per eseguire il thread principale, quindi questo sembra il modo per farlo, ma (a) come posso sapere se sono in qualche altro thread e (b) come è stato il mio il codice main-thread viene messo lì in primo luogo? Eventuali indizi sarebbero benvenuti :) –

+0

Questo sarebbe considerato un esempio di deadlocking? – Pescolly

7

Il problema nel codice è che non importa se si utilizza dispatch_sync o dispatch_async, STFail() verrà sempre chiamato, causando il test fallire.

Ancora più importante, come spiegato da BJ Homer, se è necessario eseguire qualcosa in modo sincrono nella coda principale, è necessario assicurarsi che non si sia nella coda principale o che si verifichi un dead-lock. Se sei nella coda principale, puoi semplicemente eseguire il blocco come una funzione regolare.

Spero che questo aiuti:

- (void)testSample { 

    __block BOOL didRunBlock = NO; 
    void (^yourBlock)(void) = ^(void) { 
     NSLog(@"on main queue!"); 
     // Probably you want to do more checks here... 
     didRunBlock = YES; 
    }; 

    // 2012/12/05 Note: dispatch_get_current_queue() function has been 
    // deprecated starting in iOS6 and OSX10.8. Docs clearly state they 
    // should be used only for debugging/testing. Luckily this is our case :) 
    dispatch_queue_t currentQueue = dispatch_get_current_queue(); 
    dispatch_queue_t mainQueue = dispatch_get_main_queue(); 

    if (currentQueue == mainQueue) { 
     blockInTheMainThread(); 
    } else { 
     dispatch_sync(mainQueue, yourBlock); 
    } 

    STAssertEquals(YES, didRunBlock, @"FAIL!"); 
} 
6

Se siete sulla coda principale e sincrono attendere la coda principale di essere a disposizione sarete davvero aspettare a lungo. Dovresti testare per assicurarti di non essere già sul thread principale.

2

di follow-up, in quanto

dispatch_get_current_queue() 

è ora deprecato, è possibile utilizzare

[NSThread isMainThread] 

per vedere se si è sul thread principale.

Quindi, utilizzando l'altra risposta di cui sopra, si potrebbe fare:

- (void)testSample 
{ 
    BOOL __block didRunBlock = NO; 
    void (^yourBlock)(void) = ^(void) { 
     NSLog(@"on main queue!"); 
     didRunBlock = YES; 
    }; 

    if ([NSThread isMainThread]) 
     yourBlock(); 
    else 
     dispatch_sync(dispatch_get_main_queue(), yourBlock); 

    STAssertEquals(YES, didRunBlock, @"FAIL!"); 
} 
+1

Essere sul thread principale non garantisce l'esecuzione nella coda principale. Sono cose diverse. ** La coda principale viene eseguita sul thread principale **, ma potrebbero esserci altre code in esecuzione su di esso. –

1

Pensa mai uscire di casa se si deve attendere per te di uscire prima casa? Hai indovinato! No! :]

In pratica se:

  1. Sei su FooQueue.(non deve essere main_queue)
  2. Si chiama il metodo utilizzando sync cioè in modo seriale e si desidera eseguire su FooQueue.

Non accadrà mai per la stessa ragione che non uscirai mai di casa!

Non verrà mai spedito perché deve attendere se stesso per scendere dalla coda!