Ajwhiteway
Ajwhiteway

Reputation: 1006

Swift 2 - iOS - Dispatch back to originating thread

So I have an application that fires a series of asynchronous events and then writes the results to a buffer. The problem is that I want the buffer to be written to synchronously (in the thread that spawned the asynchronous process)

skeleton code is as such

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
   }
}

The problem is the thread that calls is dataTaskWithRequest is in thread, say, 3. The completion handler fires in many different threads and causes case NSStreamEvent.HasSpaceAvailable: to be running in thread 3, plus all the threads that they existed in.

My question is: How do I make it so that self.stream(self.output, handleEvent: NSStreamEvent.hasSpaceAvailable) is called in thread 3, or what-ever the original thread was to prevent this tripping over of each other in the output phase.

Thanks in advance!

NOTE: The thread that contains the input/output handling was created with NSThread.detachNewThreadSelector

Upvotes: 9

Views: 940

Answers (2)

DerailedLogic
DerailedLogic

Reputation: 363

It won't let me comment on on the thread above (this is what I get for lurking), but one thing to be aware of is that your current code could deadlock your UI if you use waitUntilDone or performBlockAndWait.

If you go that route you need to be absolutely sure that you don't call this from the mainThread or have a fallback case that spawns a new thread.

Upvotes: 1

Ajwhiteway
Ajwhiteway

Reputation: 1006

Alright, for the curious onlooker I, with aid from comments to the question I have figured out how to do what I originally asked in the question (whether or not this ultimately gets rewritten to use GCD is a different question)

The solution (with a slightly increased scope into the code) is to use performSelector with a specific thread.

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
   }
}
}

So use performSelector on the object with the selector and use the onThread to tell it what thread to pass to. I check both before performing the selector and before doing the call to make sure that output has space available (make sure I don't trip over myself)

Upvotes: 6

Related Questions