main.async vs main.sync() vs global().async in Swift3 GCD
Asked Answered
N

4

20

Example A: This causes the app to crash.

DispatchQueue.main.async {           
    let url = URL(string: imageUrl)
    do {
         let data = try Data(contentsOf: url!)
            DispatchQueue.main.sync {
                self.imageIcon.image = UIImage(data: data)
            }
        }

Example B: But this doesn't

DispatchQueue.global().async {  
    let url = URL(string: imageUrl)
    do {
        let data = try Data(contentsOf: url!)
            DispatchQueue.main.sync {
                self.imageIcon.image = UIImage(data: data)
            }
        }

As per my knowledge:

  • x.sync means doing thing in main thread/UI thread.
  • x.async means doing in background thread.
  • Global means performing something with concurrent queue i.e Parallel task.

Question 1: Why does my app crash when I perform a task on the background thread, i.e main.async, and than call main thread to update UI?

Question 2: Is there any difference between main.async & global().async?

Nakada answered 13/10, 2017 at 14:18 Comment(4)
you seem to have your terms mixed up, main = main thread, global = background thread, sync means perform task in serial (on that queue eg main/global), async is concurrent (again concurrent tasks on a single queue), i had a nice page explaining this on the stackoverflow docs but they have shut it down now :(Valentin
"x.sync means doing thing in main thread/UI thread and x.async means doing in background thread" -- I don't think that's true, and you shouldn't think of queues as threads (they're not the same). Calling async means it doesn't block, but you're calling it on DispatchQueue.main, which is a queue guaranteed to run on the main thread.Malda
I come to know about "x.sync means doing thing in main thread/UI thread..." from the [link] stackoverflow.com/questions/42772907/…Nakada
Thanks for your help, I posted my final Conclusion.. kindly Check It..Nakada
N
25

In simple term i come to conclusion that -

  • Queue- There are 3 Types of Queue i.e. 1 Main Queue, 4 Global Queue and Any No. of Custom Queues.
  • Threads- One is Main Thread and other background threads which system provides to us.

DispatchQueue.main.async

-It means performing task in main queue with using of background thread (w/o blocking of UI) and when task finish it automatic Updated to UI because its already in Main Queue.

DispatchQueue.global().async along with global().sync

It means performing task in Global Queue with using of background thread and when task finish, than global().sync use bring the work from globalQueue to mainQueue which update to UI.

Reason of My App Crash

I was trying to bring the completed task to MainQueue by using(main.sync), but it was already on MainQueue because i hadnt switched the Queue, and this create DeadLock (MainQueue waiting for itself), causes my app crash

Nakada answered 14/10, 2017 at 3:40 Comment(1)
Sorry but so much of this answer is wrong. Why so many upvotes?Jocelin
D
21

GCD

Thread -> GCD -> Operation + OperationQueue(life cycle, dependencies between different queues, cancel)

[Sync vs Async]
[iOS Thread safe]

Grand Central Dispatch(GCD) libdispatch operates on dispatch queues DispatchQueue. It works by.first in first out(FIFO) order

DispatchQueue.<queue>.<sync/async> means run a <sync/async> task on the <queue>

Queue can be serial or concurrent. Concurrent(it is more parallel) allows to process with several task simultaneously while serial - one after another. Concurrent tasks are started at that order which they were added but can be finished at different order

GCD supports:

  • main queue - serial queue on a main thread of app which is used to working with UI
  • global queue - concurrent queues which are shared between whole iOS operation system
  • private queue - serial/concurrent queues in app scope

Main Queue

//Thread.current.qualityOfService = .userInitiated
DispatchQueue.main

Global Queues

DispatchQueue.global(qos: DispatchQoS.QoSClass = .default)

Private Queue

DispatchQueue(label: String, qos: DispatchQoS = .unspecified, attributes: DispatchQueue.Attributes = [], autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency = .inherit, target: DispatchQueue? = nil)
//Serial Queue
DispatchQueue(label: "first.myApp.com") 

//Concurrent Queue - attributes: .concurrent
DispatchQueue(label: "second.myApp.com", attributes: .concurrent)       

Quality of Service(QoS)

Every Queue has qos which determines a priority of it

  • userInteractive - the highest priority - speed over energy. Task result of which are reflected on UI. Task should be small to produce fast result. For example animation
  • userInitiated - task which is started by user and result of which is async. For example loading data/opening local file to show result on UI
  • utility - long-running calculations up to several minutes like saving data to file...
  • background - the lowest priority - energy over speed. Very long-running computations from several minutes - like sync data.

Additional QoS which you should avoid:

  • default - priority is between .userInitiated and .utility
  • unspecified - uses a QoS of outer thread(inherits the QoS of the caller). For example when you call it from main thread thread.qualityOfService == .userInitiated
func examineQoS(qos: DispatchQoS) {
    let queue = <create_queue>
    queue.async {
        print("""
                QoS
                qos: \(qos.qosClass)
                queue.qos: \(queue.qos.qosClass)
                thread.qualityOfService: \(Thread.current.qualityOfService)
                """)
    }
}

let queue = DispatchQueue.global()
let queue = DispatchQueue(label: "mySerial")
let queue = DispatchQueue(label: "myConcurrent", attributes: .concurrent)
//qos == .default
//queue.qos: unspecified
//thread.qualityOfService: .userInitiated

let queue = DispatchQueue.global(qos: qos.qosClass)
    
//qos: default
//queue.qos: unspecified
//thread.qualityOfService: .userInitiated

//qos: unspecified
//queue.qos: unspecified
//thread.qualityOfService: .userInitiated

//Others
//qos == queue.qos == thread.qualityOfService

let queue = DispatchQueue(label: "mySerial", qos: qos)
let queue = DispatchQueue(label: "myConcurrent", qos: qos, attributes: .concurrent)

//qos: default
//queue.qos: default
//thread.qualityOfService: .default

//qos: unspecified
//queue.qos: unspecified
//thread.qualityOfService: .userInitiated

//Others
//qos == queue.qos == thread.qualityOfService
  • Count of worker threads are depended on OS conditions. There are no run loop[About] for worker thread.

sync/async

sync - block a current thread and wait when it will be finished on a specified queue

async - do not block a current thread and send an execution block of code to the specificified queue

//deadline in seconds
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
    // logic
}

Common mistake: deadlock

If you call DispatchQueue.main.sync on a main thread - the app will be frozen because the calling DispatchQueue.main.sync starts waiting immediately when the dispatched block is finished (dispatched block is not started)

Some notes:

  • DispatchWorkItem - delaying/cancelling/prioritise a task inside Queue or DispatchGroup
  • DispatchGroup if you are going to execute several async tasks with a single callback even on different queues. All these task should be grouped. DispatchGroup contains thread safe counter and when it equals 0 notify is called
//create group
let group = DispatchGroup()

//case 1
DispatchQueue.<queue>.async(group: group) //

//case 2 - manual
group.enter() //<- +1
DispatchQueue.global().async { 
    //logic
    group.leave() //<- -1
}

//notification
group.notify(queue: <callback_queue>) {
    //logic             
}
  • Barrier flag inside concurrent queue for sync/async task guaranties that there is no race condition[About]. The best place for it is custom queue because does not block any others global tasks:
customQueue.async(flags: .barrier) { 
    //logic
    someProperty = someValue
}
  1. all task which were started are finished
  2. Single Barrier task
  3. Executing all other tasks in the queue

thread safe operation can be reached through Barrier in concurrent queue for shared variable:

  • read - sync operation on concurrent queue
  • write - async operation with barrier

[Thread safe singleton]

Dibbell answered 8/4, 2020 at 14:21 Comment(0)
D
8

In first case, you run the code on main and then you use main.sync on the main thread. In essence, you are trying to tell the main queue to wait for itself - which is obviously nonsense and therefore it causes crash.

In the second case, you run the code on the background thread, and then you use main.sync to wait until the main thread can run the block provided in main.sync.

In general, I would use async and not sync all the time, unless sync is necessary - and always sync one thread (DispatchQueue) with a different one, never with the same one.

Deragon answered 13/10, 2017 at 14:27 Comment(2)
yeah, sync just means wait here until this block has finished executing, async does not wait. Must be careful using sync while on a main thread otherwise it will block your UI updatesValentin
I know that global().async mean performing task in background, but what does main.async means..??Nakada
B
4

You were mixing up the terms sync/async and main/global.

Sync - Run some task synchronously (i.e. the thread which can be main/global/any other thread will wait for the task to complete)

Async - Run some task asynchronously (i.e. the thread which can be main/global/any other thread will push the task to a queue and continue executing next steps outside your block. It wont wait)

Now lets go one by one in your code which was crashing :
Lets put some names for our threads so it will be easy for our understanding:
1) ThreadA - Which encounters your dispatch statements (this can also be Main thread, but for explanation purpose I feel its better)
2) ThreadB - Global thread which gets created when you submit some task.
3) ThreadMain - Main thread

Example A:
DispatchQueue.main.async - ThreadA comes and execute this statement and put your block on ThreadMain and moves on (since its async) to next steps after the block.

Now lets talk about ThreadMain, what it will do from here. Since ThreadMain got a block (submitted by ThreadA) it starts executing step by step and suddenly it sees 'DispatchQueue.main.sync' and submits the inner block on to the same TheradMain queue and keeps onnnnn waitingggggg (since its sync). So literally you are making the ThreadMain into deadlock situation.

Brisket answered 13/10, 2017 at 16:3 Comment(2)
main.async perform task in background and than sync work with MainThread, Am i right??...Nakada
@ShivamSrivastava No. main.async does not perform the task in the background. It is performed on the main queue/thread.Jocelin

© 2022 - 2024 — McMap. All rights reserved.