Skip to content

New Threading and Context Stack model

Elom Gomez edited this page Mar 17, 2016 · 10 revisions

One of the primary pieces of confusion about how to use CDQ comes up around threading, specifically, doing work anywhere but the main thread. The CDQ API hasn't supported it very well, and starting in 1.0, we aim to fix that.

One-off Worker Tasks

There are two basic models for working in the background: persistent threads, and one-off asynchronous tasks. We're going to look at the latter first. Here's what you had to do in 0.x:

  # Create a new private queue context with the main context
  # as its parent, then pop it back off the stack.
  cdq.contexts.new(NSPrivateQueueConcurrencyType) do
    context = cdq.contexts.current

    # any work on a private context must be passed to performBlock
    context.performBlock(-> {
      cdq.contexts.push(context) do

        # your stuff

        cdq.save(always_wait: true)  # only save the current thread's stack

        # Go save the main context on the main thread
        Dispatch::Queue.main.async do
          cdq.save
        end
      end
    })
  end

This works fine, but it's a tad obscure. You have to push the context around yourself because it (intentionally) gets lost when you switch threads, and the use of the new method is confusing. So starting in version 1.0, this how you would do the same thing:

    cdq.background do
      # your stuff

      cdq.save(always_wait: true)  # only save the current thread's stack

      # Go save the main context on the main thread (optional)
      cdq.save(:main)
    end

We're also deprecating new in favor of push, which now accepts a concurrency type as its argument, in which case it acts more or less like new did. There's a new method, create, that adds a child context without pushing it onto the stack.

Persistent background threads

One-off tasks are generally the way to go for most things because they're simple, but sometimes you need so keep state between tasks. So the model for that is to have a persistent thread in the background which will update data periodically, such as a network listener. Core Data's private queue contexts already work that way, but we've added some support to make it easier.

The first is named contexts. Named contexts are available directly off of the contexts object, like so: cdq.contexts.network. They may or may not exist in the context stack. If they're not on the stack, you'll have to save them directly: cdq.contexts.network.save(nil), or use a handy new shortcut: cdq.save(:network).
Named contexts are global and permanent.

The second is the on method, which is a bit of syntactic sugar for calling performBlock as required for private contexts.

Putting them together:

  cdq.contexts.push(:root)
  cdq.contexts.push(:main)
  cdq.contexts.push(:private, named: :network)

  cdq.contexts.on(:network) do
    # stuff
    cdq.save(:network, always_wait: true)
    cdq.save(:main)
  end

As you may have noticed, we've also added symbol shortcuts for concurrency types, so you can use :private and :main in place of the verbose cocoa constants. Since there should only ever be one, any context of type :main implicitly names itself main. There's also a special synonym of :private called :root that also implicitly names itself.