9

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

Ajwhiteway
  • 941
  • 1
  • 8
  • 21
  • 2
    How about creating your own queue(s) and passing the one that should be used to your writer? (I say "queue" because you tagged with gcd.) – Phillip Mills Jan 15 '16 at 20:50
  • 1
    I thought about doing that about 5 minutes before I had to leave work. Create the thread/queue and funnel all output to that. That way even if there are multiple threads calling in they aren't all trying to execute at once. I'm off until Tuesday now so if that is the route I go I will let you know how it goes – Ajwhiteway Jan 15 '16 at 21:09
  • I already have a NSThread created by NSThread.detachNewThreadSelector. This is the thread that I need to call back into. I fear dispatching may actually worsen the problem (the streams closing before everything is finished) – Ajwhiteway Jan 20 '16 at 13:58
  • If you're using `NSThread.detachNewThreadSelector` in code written after iOS 4, you're doing something wrong. If you're using it in older code than that (or pre-10.6 code), you're *still* probably doing something wrong. The fact that you're even discussing the specific thread something runs on in ObjC code means you're approaching the problem incorrectly (and that's why you're having these problems). Start by reading Apple's "Migrating Away From Threads" (https://developer.apple.com/library/ios/documentation/General/Conceptual/ConcurrencyProgrammingGuide/ThreadMigration/ThreadMigration.html). – Rob Napier Jan 20 '16 at 14:13
  • 1
    If you want a good example of correctly using streams with GCD (via CFStream, but this is very similar to NSStream), see the GCD code in CocoaAsyncSocket. https://github.com/robbiehanson/CocoaAsyncSocket/tree/master/Source/GCD. This is the best library I know of for managing network sockets. – Rob Napier Jan 20 '16 at 14:17
  • Thanks, I'll have a look at it. I was looking at using performSelector but that doesn't seem to do what I need. Ideally I would like to do this without re-writing my small 1000 line 'server' – Ajwhiteway Jan 20 '16 at 14:31
  • Mulling it over with my co-worker and I think I will end up re-writing it to run through Grand Central Dispatch rather than NSThread. Thanks! This is my first iOS/Swift project so there was bound to be some error in implementation. Can you recommend any other references to look at before re-writing to guard against other pitfalls? – Ajwhiteway Jan 20 '16 at 14:42
  • 1
    I would use `performSelector` family over GCD when possible, since it hides and handles `waitUntilDone` for you, Here is a discussion you may find useful: http://stackoverflow.com/a/34540787/218152. – SwiftArchitect Jan 21 '16 at 17:17
  • Thank you, I'll have a second look at performSelector! – Ajwhiteway Jan 21 '16 at 18:17
  • If you're trying to time multiple asynchronous events, you could look at using dispatch semaphores. – Michael Jan 22 '16 at 00:32

2 Answers2

6

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)

Ajwhiteway
  • 941
  • 1
  • 8
  • 21
  • I would stick with `performSelector`, since it wraps GCD, and solves conundrums around dispatching from same or different queues. Of interest is `waitUntilDone: false`, which I generally prefer to be `true` in order to ensure proper sequencing of operations. – SwiftArchitect Jan 26 '16 at 17:00
  • Yeah, I've wrapped up the project for testing with `performSelector` still in use. Because of how these spawn, there is typically only one per thread (`dataTaskWithRequest` handles that). So by making it `false` I'm allowing the thread to be cleared up once the data has been added to the queue for the buffer. Still has-to go through some testing to make sure everything is holding together when put into the larger project. – Ajwhiteway Jan 26 '16 at 17:04
  • You seem to be in full control! My experience with `performSelector` and `performBlock` is that I generally regret saving milliseconds here and there, and must revert to `waitUntilDone` and `performBlockAndWait` for robustness and predictability. – SwiftArchitect Jan 26 '16 at 17:08
  • That's a good note for any reader. `waitUntilDone = false` would be the exception not the standard. – Ajwhiteway Jan 26 '16 at 17:27
1

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.

DerailedLogic
  • 363
  • 1
  • 6
  • This is a good point that I haven't explicitiy handled in my code, in my case `dataTaskWithRequest `'s completion handler should never end up inside the main GUI thread. In any case a good point. Since you were the only person other than myself to answer the question, enjoy the bounty reward! – Ajwhiteway Jan 27 '16 at 15:19