11

Voglio avvolgere un'API asincrona che assomigliano a questo:Come avvolgere un metodo asincrono che prende un blocco e ruotarla sincrono c obiettivo

[someObject completeTaskWithCompletionHandler:^(NSString *result) { 

}]; 

in un metodo sincrono che posso chiamare in questo modo

NSString *result = [someObject completeTaskSynchronously]; 

Come posso fare questo? Ho fatto qualche lettura doc e Google Search, e tentativo di utilizzare "dispatch_semaphore" fare cercano di raggiungere in questo modo:

-(NSString *) completeTaskSynchronously { 
    __block NSString *returnResult; 
    self.semaphore = dispatch_semaphore_create(0); 
    [self completeTaskWithCompletionHandler:^(NSString *result) { 
     resultResult = result; 
     dispatch_semaphore_signal(self.semaphore); 
    }]; 

    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER); 
    return resultResult; 
} 

Ma questo doesnt sembrano funzionare, è fondamentalmente solo fermare al dispatch_semaphore_wait. L'esecuzione non raggiunge mai il blocco interno che fa il segno. Qualcuno ha un esempio di codice su come farlo? Sospetto che il blocco debba essere su un thread diverso rispetto al thread principale? Inoltre, supponiamo di non avere accesso al codice sorgente dietro il metodo asincrono. Grazie!

+4

Se gestore di completamento viene eseguita sullo stesso thread che chiama dispatch_semaphore_wait si in effetti un punto morto il filo perché il blocco di completamento non può essere eseguito fino a quando il thread da l'attesa. Stai cercando di farlo sul thread principale? È meglio non bloccare il thread principale per molto tempo perché deve inviare messaggi in modo costante. – yurish

+0

Se, come sospettato da @yurish, il gestore è stato accodato al thread di invio principale, non devi aspettare. Devi costruire il tuo flusso di codice come una macchina di stato e fare tutto ciò che deve essere fatto con risultato nel gestore di completamento. –

+2

Non c'è un modo generale per farlo. Come altri hanno già detto, se una parte dell'attività asincrona funziona mettendo eventi nel ciclo di esecuzione, si verificherà sempre un deadlock. Cosa stai cercando di ottenere? Forse c'è un altro modo per strutturare il tuo codice. – JeremyP

risposta

-2

Si può provare a utilizzare NSOperations per fare ciò in modo asincrono.

8

dispatch_semaphore_wait blocca la coda principale nell'esempio. Si può inviare il compito asincrona a una coda diversa:

__block NSString *returnResult; 
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0UL); 
dispatch_async(queue,^{ 
    result = [someObject completeTaskSynchronously]; 
}); 

o usare qualche altro sistema, come NSRunLoop:

__block finished = NO; 
    [self completeTaskWithCompletionHandler:^(NSString *result) { 
     resultResult = result; 
     finished = YES; 
    }]; 
    while (!finished) { 
     // wait 1 second for the task to finish (you are wasting time waiting here) 
     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]]; 
    } 
6

Utilizzando un NSRunLoop è il più facile da fare qui.

__block NSString* result = nil; 
[self completeTaskWithCompletionHandler:^(NSString *resultstring) { 
    result = resultstring; 
}]; 
while (!result) { 
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; 
} 
+0

Sì, penso che funzioni bene. Ho anche trovato un MTTestSemaphore di classe su github che incapsula questo e lo rende più simile a un semaforo. Grazie. – kawingkelvin

+0

Mi chiedo quanto bene questo interagisca con l'elaborazione dell'evento normale? Cose come transazioni Core Animation ecc. – yurish

+3

Questo non può funzionare bene. Il ciclo di esecuzione richiede che gli eventi vengano elaborati per poter tornare. Se non ci sono eventi, ritornerà solo nel "lontano futuro", cioè * mai *. Inoltre, l'accesso al puntatore 'result' non è thread-safe. Non è garantito che un puntatore non sia zero E completamente inizializzato. E (possibilmente) un compilatore può essere autorizzato a ottimizzare l'accesso alla posizione di memoria e mantenere il puntatore in un registro che non viene mai aggiornato. – CouchDeveloper

1

Penso che la soluzione meglio sarà NSRunLoop come indicato di seguito. La sua multa semplice e funzionante.

- (NSString *)getValue { 

    __block BOOL _completed = NO; 
    __block NSString *mValue = nil; 


    [self doSomethingWithCompletionHandler:^(id __nullable value, NSError * __nullable error) { 
     mValue = value; 
     _completed = YES; 
    }]; 

    while (!_completed) { 
     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; 
    } 

    return mValue; 
} 
Problemi correlati