2016-01-15 11 views
9

Quindi ho un'applicazione che attiva una serie di eventi asincroni e quindi scrive i risultati in un buffer. Il problema è che voglio il buffer da scrivere sincrono (nel thread che ha generato il processo asincrono)Swift 2 - iOS - Ritorno al thread di origine

codice scheletro è come tale

let Session = NSURLSession.sharedSession() 
let TheStack = [Structure]() 
//This gets called asynchronously, e.g. in threads 3,4,5,6,7 
func AddToStack(The Response) -> Void { 
    TheStack.insertAt(Structure(The Response), atIndex: 0)) 
    if output.hasSpaceAvailable == true { 
     // This causes the stream event to be fired on mutliple threads 
     // This is what I want to call back into the original thread, e.g. in thread 2 
     self.stream(self.output, handleEvent: NSStreamEvent.hasSpaceAvailable) 
    } 
} 

// This is in the main loop, e.g. thread 2 
func stream(aStream: NSStream, handleEvent: NSStreamEvent) { 

    switch(NSStreamEvent) { 

     case NSStreamEvent.OpenCompleted: 
      // Do some open stuff 
     case NSStreamEvent.HasBytesAvailable: 
      Session.dataTaskWithRequest(requestFromInput, completionHandler: AddToStack) 
     case NSStreamEvent.HasSpaceAvailable: 
      // Do stuff with the output 
     case NSStreamEvent.CloseCompleted: 
      // Close the stuff 
    } 
} 

il problema è il filo che chiama è dataTaskWithRequest è in thread, say, 3. Il gestore di completamento si attiva in molti thread diversi e causa case NSStreamEvent.HasSpaceAvailable: nell'esecuzione nel thread 3, oltre a tutti i thread in cui erano presenti.

La mia domanda è: come faccio a fare in modo che self.stream(self.output, handleEvent: NSStreamEvent.hasSpaceAvailable) sia chiamato nel thread 3, o quale sia il thread originale w per evitare che si inciampino l'un l'altro nella fase di uscita.

Grazie in anticipo!

NOTA: Il filo che contiene la gestione di input/output è stato creato con NSThread.detachNewThreadSelector

+2

Come creare le proprie code e passare quella che dovrebbe essere utilizzata per lo scrittore? (Dico "coda" perché hai taggato con gcd.) –

+1

Ho pensato di farlo circa 5 minuti prima che dovessi lasciare il lavoro. Crea il thread/coda e canalizza tutto il risultato. In questo modo, anche se ci sono più thread che chiamano, non tutti cercano di eseguire contemporaneamente. Sono fuori fino a martedì ora, quindi se questa è la strada che andrò ti farò sapere come va – Ajwhiteway

+0

Ho già un NSThread creato da NSThread.detachNewThreadSelector. Questo è il thread di cui ho bisogno per richiamare. Temo che l'invio possa effettivamente peggiorare il problema (i flussi si chiudono prima che tutto sia finito) – Ajwhiteway

risposta

6

Va bene, per i curiosi osservatore che, con l'aiuto commenti alla domanda che ho capito come fare quello che in origine nella domanda (indipendentemente dal fatto che questo venga riscritto o meno per utilizzare GCD è una domanda diversa)

La soluzione (con un ambito leggermente aumentato nel codice) consiste nell'utilizzare performSelector con una thread specifica.

final class ArbitraryConnection { 

internal var streamThread: NSThread 

let Session = NSURLSession.sharedSession() 
let TheStack = [Structure]() 
//This gets called asynchronously, e.g. in threads 3,4,5,6,7 
func AddToStack(The Response) -> Void { 
    TheStack.insertAt(Structure(The Response), atIndex: 0)) 
    if output.hasSpaceAvailable == true { 
     // This causes the stream event to be fired on multiple threads 
     // This is what I want to call back into the original thread, e.g. in thread 2 

     // Old way 
     self.stream(self.output, handleEvent: NSStreamEvent.hasSpaceAvailable) 
     // New way, that works 
     if(streamThread != nil) { 
      self.performSelector(Selector("startoutput"), onThread: streamThread!, withObject: nil, waitUntilDone: false) 
     } 
    } 
} 

func open -> Bool { 
    // Some stuff 
    streamThread = NSThread.currentThread() 
} 


final internal func startoutput -> Void { 
    if(output.hasSpaceAvailable && outputIdle) { 
     self.stream(self.output, handleEvent: NSStreamEvent.HasSpaceAvailable) 
    } 
} 
// This is in the main loop, e.g. thread 2 
func stream(aStream: NSStream, handleEvent: NSStreamEvent) { 

    switch(NSStreamEvent) { 

     case NSStreamEvent.OpenCompleted: 
      // Do some open stuff 
     case NSStreamEvent.HasBytesAvailable: 
      Session.dataTaskWithRequest(requestFromInput, completionHandler: AddToStack) 
     case NSStreamEvent.HasSpaceAvailable: 
      // Do stuff with the output 
     case NSStreamEvent.CloseCompleted: 
      // Close the stuff 
    } 
} 
} 

Così utilizzare performSelector sull'oggetto con il selettore e utilizzare l'onThread indicare cosa filo di passare a. Controllo entrambi prima di eseguire il selettore e prima di effettuare la chiamata per assicurarmi che l'output abbia spazio disponibile (assicurati di non inciampare)

+0

Rimanere con 'performSelector', poiché avvolge GCD e risolve enigmi attorno alla distribuzione da code uguali o diverse. Di interesse è 'waitUntilDone: false', che in genere preferisco essere' true' per garantire il corretto sequenziamento delle operazioni. – SwiftArchitect

+0

Sì, ho concluso il progetto per il test con 'performSelector' ancora in uso. A causa di come questi spawn, di solito c'è solo uno per thread ('dataTaskWithRequest' lo gestisce). Quindi rendendolo 'false' sto permettendo che il thread venga cancellato una volta che i dati sono stati aggiunti alla coda per il buffer. È ancora necessario sottoporsi ad alcuni test per assicurarsi che tutto rimanga unito quando inserito nel progetto più ampio. – Ajwhiteway

+0

Sembra che tu abbia il pieno controllo! La mia esperienza con 'performSelector' e' performBlock' è che generalmente mi pento di aver salvato millisecondi qua e là, e devo tornare a 'waitUntilDone' e' performBlockAndWait' per robustezza e prevedibilità. – SwiftArchitect

1

Non mi consentirà di commentare la discussione sopra (questo è quello che ottengo per in agguato), ma una cosa di cui essere a conoscenza è che il codice corrente potrebbe deadlock l'interfaccia utente se si utilizza waitUntilDone o performBlockAndWait.

Se segui questa strada, devi essere assolutamente sicuro di non chiamare questo dal MainThread o di avere un caso di riserva che genera un nuovo thread.

+0

Questo è un buon punto che non ho explicitiy gestito nel mio codice, nel mio caso il gestore di completamento 'dataTaskWithRequest' non dovrebbe mai finire all'interno del thread della GUI principale. In ogni caso, un buon punto. Poiché tu eri l'unica persona diversa da me per rispondere alla domanda, goditi la ricompensa generosa! – Ajwhiteway