Swift 2 - iOS - Dispatch back to originating thread
Asked Answered
M

2

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

Manifestative answered 15/1, 2016 at 19:57 Comment(10)
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.)Importune
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 goesManifestative
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)Manifestative
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" (developer.apple.com/library/ios/documentation/General/…).Hygroscope
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. github.com/robbiehanson/CocoaAsyncSocket/tree/master/Source/GCD. This is the best library I know of for managing network sockets.Hygroscope
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'Manifestative
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?Manifestative
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: https://mcmap.net/q/45398/-performselector-may-cause-a-leak-because-its-selector-is-unknown.Simonson
Thank you, I'll have a second look at performSelector!Manifestative
If you're trying to time multiple asynchronous events, you could look at using dispatch semaphores.Ruvalcaba
M
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)

Manifestative answered 22/1, 2016 at 13:31 Comment(4)
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.Simonson
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.Manifestative
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.Simonson
That's a good note for any reader. waitUntilDone = false would be the exception not the standard.Manifestative
O
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.

Octangle answered 27/1, 2016 at 0:56 Comment(1)
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!Manifestative

© 2022 - 2024 — McMap. All rights reserved.