Atif
Atif

Reputation: 286

How to ensure to run some code on same background thread?

I am using realm in my iOS Swift project. Search involve complex filters for a big data set. So I am fetching records on background thread.

But realm can be used only from same thread on which Realm was created. I am saving a reference of results which I got after searching Realm on background thread. This object can only be access from same back thread

How can I ensure to dispatch code at different time to the same thread?

I tried below as suggested to solve the issue, but it didn't worked

let realmQueue = DispatchQueue(label: "realm")
    var orginalThread:Thread?

    override func viewDidLoad() {
        super.viewDidLoad()

        realmQueue.async {
            self.orginalThread = Thread.current
        }

        let deadlineTime = DispatchTime.now() + .seconds(2)
        DispatchQueue.main.asyncAfter(deadline: deadlineTime) {
            self.realmQueue.async {
                print("realm queue after some time")

                if self.orginalThread == Thread.current {
                    print("same thread")
                }else {
                    print("other thread")
                }
            }
        }
    }

Output is

realm queue after some time

other thread

Upvotes: 8

Views: 3108

Answers (2)

Cristik
Cristik

Reputation: 32894

Here's a small worker class that can works in a similar fashion to async dispatching on a serial queue, with the guarantee that the thread stays the same for all work items.

// Performs submitted work items on a dedicated thread
class Worker {

    // the worker thread
    private var thread: Thread?

    // used to put the worker thread in the sleep mode, so in won't consume
    // CPU while the queue is empty
    private let semaphore = DispatchSemaphore(value: 0)

    // using a lock to avoid race conditions if the worker and the enqueuer threads
    // try to update the queue at the same time
    private let lock = NSRecursiveLock()

    // and finally, the glorious queue, where all submitted blocks end up, and from
    // where the worker thread consumes them
    private var queue = [() -> Void]()

    // enqueues the given block; the worker thread will execute it as soon as possible
    public func enqueue(_ block: @escaping () -> Void) {
        // add the block to the queue, in a thread safe manner
        locked { queue.append(block) }

        // signal the semaphore, this will wake up the sleeping beauty
        semaphore.signal()

        // if this is the first time we enqueue a block, detach the thread
        // this makes the class lazy - it doesn't dispatch a new thread until the first
        // work item arrives
        if thread == nil {
            thread = Thread(block: work)
            thread?.start()
        }
    }

    // the method that gets passed to the thread
    private func work() {
        // just an infinite sequence of sleeps while the queue is empty
        // and block executions if the queue has items
        while true {
            // let's sleep until we get signalled that items are available
            semaphore.wait()

            // extract the first block in a thread safe manner, execute it
            // if we get here we know for sure that the queue has at least one element
            // as the semaphore gets signalled only when an item arrives
            let block = locked { queue.removeFirst() }
            block()
        }
    }

    // synchronously executes the given block in a thread-safe manner
    // returns the same value as the block
    private func locked<T>(do block: () -> T) -> T {
        lock.lock(); defer { lock.unlock() }
        return block()
    }
}

Just instantiate it and let it do the job:

let worker = Worker()
worker.enqueue { print("On background thread, yay") }

Upvotes: 18

clemens
clemens

Reputation: 17712

You have to create your own thread with a run loop for that. Apple gives an example for a custom run loop in Objective C. You may create a thread class in Swift with that like:

class MyThread: Thread {
    public var runloop: RunLoop?
    public var done = false

    override func main() {
        runloop = RunLoop.current
        done = false
        repeat {
            let result = CFRunLoopRunInMode(.defaultMode, 10, true)
            if result == .stopped  {
                done = true
            }
        }
        while !done
    }

    func stop() {
        if let rl = runloop?.getCFRunLoop() {
            CFRunLoopStop(rl)
            runloop = nil
            done = true
        }
    }
}

Now you can use it like this:

    let thread = MyThread()

    thread.start()
    sleep(1)
    thread.runloop?.perform {
        print("task")
    }
    thread.runloop?.perform {
        print("task 2")
    }
    thread.runloop?.perform {
        print("task 3")
    }

Note: The sleep is not very elegant but needed, since the thread needs some time for its startup. It should be better to check if the property runloop is set, and perform the block later if necessary. My code (esp. runloop) is probably not safe for race conditions, and it's only for demonstration. ;-)

Upvotes: 2

Related Questions