This is explained pretty well in the dispatch/queue.h header:
DISPATCH_QUEUE_PRIORITY_HIGH
Items dispatched to the queue will run at high priority,
i.e. the queue will be scheduled for execution before
any default priority or low priority queue.
DISPATCH_QUEUE_PRIORITY_DEFAULT
Items dispatched to the queue will run at the default
priority, i.e. the queue will be scheduled for execution
after all high priority queues have been scheduled, but
before any low priority queues have been scheduled.
DISPATCH_QUEUE_PRIORITY_LOW
Items dispatched to the queue will run at low priority,
i.e. the queue will be scheduled for execution after all
default priority and high priority queues have been
scheduled.
DISPATCH_QUEUE_PRIORITY_BACKGROUND
Items dispatched to the queue will run at background priority, i.e. the queue
will be scheduled for execution after all higher priority queues have been
scheduled and the system will run items on this queue on a thread with
background status as per setpriority(2) (i.e. disk I/O is throttled and the
thread's scheduling priority is set to lowest value).
And keep in mind this is a global queue. Other things, like system frameworks, may be scheduling in to it. It's very easy to starve the priority bands - if there are a lot of DISPATCH_QUEUE_PRIORITY_HIGH
tasks being scheduled, tasks at the default priority may have to wait quite a while before executing. And tasks in DISPATCH_QUEUE_PRIORITY_BACKGROUND may have to wait a very long time, as all other priorities above them must be empty.
A lot of developers abuse the global concurrent queue. They want a execute a block, need a queue, and just use that at the default priority. That kind of practice can lead to some very difficult to troubleshoot bugs. The global concurrent queue is a shared resource and should be treated with care. In most cases it makes more sense to create a private queue.
A concurrent queue is not asynchronous, it is concurrent. Synchronous tasks can still be scheduled into it, and they will still execute synchronously. Concurrent queues, like serial queues, dequeue in FIFO order. They execute blocks concurrently, unlike serial queues. Concurrent and asynchronous are not the same thing.
Also keep in mind that if the main thread is idle, a concurrent queue can re-use that thread - and in fact will prefer doing that to creating new threads. Using a concurrent queue will not guarantee you will not block the user interface:
Blocks submitted to these dispatch queues are invoked on a pool
of threads fully managed by the system. No guarantee is made regarding
which thread a block will be invoked on; however, it is guaranteed that only
one block submitted to the FIFO dispatch queue will be invoked at a time.
GCD makes no guarantees about what thread will be used to execute a block on a concurrent queue. If you use the main queue, the block will be executed serially on the main thread. A concurrent queue can use any thread, and as an optimization will prefer to use existing threads. It will only create a new thread if no threads are available to be reused. And in fact the main thread is often it's first choice (if the main thread is available for work) because it is "warm".
To reiterate:
With Grand Central Dispatch you can be certain that a task will execute on the main thread (by submitting to the main queue).
You cannot be certain that a task will not execute on the main thread.