1

I perform URLSession.shared.downloadTask request, but would like to execute code on the same thread the downloadTask was executed on. For example:

func sample() {
   let thread = Thread.current

   URLSession.shared.downloadTask(with: file) {
      someFunc() //How to execute on thread variable?
   }.resume()
}

In the downloadTask completion handler, it is running on a background thread. However, I'd like to call someFunc() on the same thread sample() was called on. How do I do something like Thread.current.async {...} so I can do this:

func sample() {
   let thread = Thread.current

   URLSession.shared.downloadTask(with: file) {
      thread.async { someFunc() } //Doesn't compile
   }.resume()
}
TruMan1
  • 33,665
  • 59
  • 184
  • 335

2 Answers2

3

If you want to run something on a particular Thread, you wouldn't use this GCD API, but rather just:

perform(#selector(someMethod), on: thread, with: nil, waitUntilDone: false, modes: [RunLoopMode.commonModes.rawValue])

That of course assumes that you created a thread with a run loop, e.g.:

let thread = Thread(target: self, selector: #selector(threadEntryPoint), object: nil)
thread.start()

and

func threadEntryPoint() {
    autoreleasepool {
        Thread.current.name = "com.domain.app.background"
        let runLoop = RunLoop.current
        runLoop.add(NSMachPort(), forMode: .defaultRunLoopMode)
        runLoop.run()
    }
}

For more information, see the Threading Programming Guide.

Personally, I'd personally stay within GCD if humanly possible, but you said elsewhere that you have some unique requirement that precludes that.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • This may solve the problem but threads are very low-level and complicated. You have to modify the invocation of the `sample` method to be passed to this specific thread via `perform on:`. You may have to deal with out of order events created by other threads between `sample` and `someFunc` ... The same can be archived using high-level APIs like `OperationQueue` which should be the preferred way. – andih Feb 06 '17 at 09:06
  • I agree, which is why I closed with "use GCD if you can". This is not, though, a good use case for operation queues. And blocking the calling thread, like you do in your answer, is never a good idea. – Rob Feb 06 '17 at 12:02
  • You are right, the blocking approach should be avoided. Another approach which avoids blocking would be to use dependent Operations. – andih Feb 06 '17 at 12:52
  • As the `OperationQueue` [documentation says](https://developer.apple.com/reference/foundation/operationqueue), "Operation queues use the `libdispatch` library (also known as Grand Central Dispatch) to initiate the execution of their operations." So I don't believe you have any greater assurances as to which thread an operation queue uses than with dispatch queues. The only time you know precisely which thread is being used is when you use the main queue. – Rob Feb 07 '17 at 18:19
  • Why do you use `autoreleasepool` in the stack function `threadEntryPoint`. `threadEntryPoint` runs in a stack space, so I think it's not necessary. – Roger Mar 20 '22 at 11:23
  • [The docs](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html#//apple_ref/doc/uid/10000057i-CH15-SW17) are explicit that “creating an autorelease pool should be the first thing you do in your thread entry routine.” The fact that you have a stack has absolutely nothing to do with whether there are autorelease objects instantiated behind the scenes (which these old API calls often do) or not. So, it is prudent for the thread entry routine to create a pool, like you always do in this old-school threading pattern. – Rob Mar 20 '22 at 15:45
0

There is nothing like Thread.current.async that's the reason why it does not compile.

If you want to run sample and someFunc on the same thread you can use an Operation and an OperationQueue. Disadvantage of this approach, you ar blocking one thread.

var myQueue:OperationQueue =  {
    let q = OperationQueue()
    q.name = "downloadQueue"
    // if you want to limit concurrent operation
    q.maxConcurrentOperationCount = 5 
    return q
}()


class DowloadOperation :Operation { 

    enum State {
        case Ready, Executing, Finished
        // ...
    }

    var state = State.Ready {
        willSet {
          ...
        }
        didSet {
          ...
        }
    }    

    override func start() {
        if self.isCancelled {
            state = .Finished
        } else {
            state = .Ready
            main()
        }
    }

    override var isFinished: Bool {
        get {
            return state == .Finished
        }
    }

    override var isAsynchronous: Bool {
        get {
            return true
        }
    }

    override var isExecuting: Bool {
        get {
            return state == .Executing
        }
    }


    override func main() {
        if self.isCancelled {
            self.state == .Finished
            return
        }

        state = .Executing

        URLSession.shared.downloadTask(with: file) {
           // ... 
           state = .Finished
        }

      }.resume()
   }
}

func someFunc() {
   ...
}

// sample and someFunc are executed on the same thread
// you loose the advantage of the async download.
func sample() {
   downloadOp = DownloadOperation()
   myQueue.addOperation(downloadOp)
   downloadOp.waitUntilFinished()
   someFunc()
}
andih
  • 5,570
  • 3
  • 26
  • 36
  • Needless to say, if you do asynchronous `Operation` subclass (which is a great pattern), readers should note that they must do the necessary key-value notifications. I'm sure you intended to do that where you have your ellipses in `willSet` and `didSet`, but that's an essential detail or else dependencies, and the like, simply won't work. – Rob Feb 07 '17 at 19:06