-1

Say I have several database write closures and I want to execute them on a single thread, but not as a batch - I need to update UI after each write.

Serial queues like:

DispatchQueue.global(qos: .background).async {}

or

DispatchQueue(label: "hello world").async {}

run in whatever thread they feel like running, serially though.

How can I have a queue that runs only on one background thread?

Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
Ivan
  • 1,320
  • 16
  • 26
  • What does it matter on what thread is runs on, as long it's not the main thread? Whats the use case? – J. Doe Jul 10 '19 at 11:46
  • Using one core data context, or one realm. – Ivan Jul 10 '19 at 12:52
  • 3
    I can do any work on any thread of CoreData without errors when using a perform bock on a context. It executes it's work on it's own queue. I think you are making a mistake trying to execute on 1 thread. It doesn't matter on what thread something is executed, but the queue is what matters. – J. Doe Jul 10 '19 at 14:12
  • I appreciate your effort, it will be vey helpful if you can provide a code sample of your approach, but still Realm has significant performance benefit when used in one thread only. And I am really curious about a way to archive this behaviour. – Ivan Jul 10 '19 at 15:18
  • Looks like duplication indeed. Still no answer though. – Ivan Aug 20 '19 at 08:10

1 Answers1

3

As others have pointed out, which thread the code runs on really doesn't matter. Performance issues like this are typically dependent on simply running the tasks sequentially, one at a time, so they don't overlap or collide with resources.

The simplest solution is to create a sequential queue. (typed in Safari, so worth every penny you paid for it)

let queue = DispatchQueue(label: "db.update", qos: .utility, attributes: [], autoreleaseFrequency: .inherit, target: nil)

var progress: Int = 0

queue.async {
    // Dome some work, then update the UI
    progress = 2
    DispatchQueue.main.async(execute: {
        // label.text = "did something"
        // progress.doubleValue = Double(progressCounter)
    })
}

queue.async {
    // Do some more work
    progress += 1
    // This won't execute until the first block has finished
}

queue.async {
    // Even more work
    progress += 1
}

queue.async {
    // And so on...
    progress += 1   // This block might, for example, notify the system that everything has finished
    print("progress is finally \(progress)")
}

The key is that each block executes sequentially (because the queue is not "concurrent"), and the next block won't start until the previous one has finished. Each block may, or may not, execute on the same thread, but it should't matter.

The results/progress of one block can easily be passed to the next block via closure variables.

James Bucanek
  • 3,299
  • 3
  • 14
  • 30
  • Say I have a request that returns a number N, and on receive it spawns N requests each of which M requests in turn and so on. I have a progress indicator that shows a number of total and loaded. I have to parse data and persist it in the background to keep my indicator responsive. The question is not that much about consistency of data - if writes are executed serially there is no problem, its about a technical side of persisting APIs. – Ivan Jul 11 '19 at 06:36
  • If I have one NSManagedObjectContext or one Realm for one async result that stores a record and another request that reads it before sending over network they will end up being in different thread(mostly likely). If you run your schema with core data concurrency debug flag this will fail. With Realm I am not sure maybe you just will not see previous records. So what am I trying to say is that when you have nontrivial tree of network request it is very convenient to integrate their results and parameters using model/storage/context. – Ivan Jul 11 '19 at 06:36
  • Otherwise it is very tedious to pass stuff between closures and sync their results. I personally ended up by having a separate NSManagedObjectContext for each request, then passing NSManagedObjectIDs from closure to closure to fetch data in future requests closures. – Ivan Jul 11 '19 at 06:37
  • It's easy to share results between blocks using closure variables. I'll update the example code to show that. – James Bucanek Jul 11 '19 at 18:07
  • James, a few observations. First, you said a “sequential queue”. I know what you mean, but I’d suggest the term “serial queue”. Second, all of these `sync` calls should really be `async`. There’s no reason why the thread that dispatched these database writes should be waiting for the database write to finish. – Rob Aug 19 '19 at 16:10
  • 1
    @Rob, you're right, those should be `async` calls. The hazards of composing code in Safari... I'll update the code to reflect this. – James Bucanek Aug 19 '19 at 20:22