In Swift How to Call Method with Parameters on Gcd Main Thread

In Swift how to call method with parameters on GCD main thread?

Modern versions of Swift use DispatchQueue.main.async to dispatch to the main thread:

DispatchQueue.main.async { 
// your code here
}

To dispatch after on the main queue, use:

DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
// your code here
}

Older versions of Swift used:

dispatch_async(dispatch_get_main_queue(), {
let delegateObj = UIApplication.sharedApplication().delegate as YourAppDelegateClass
delegateObj.addUIImage("yourstring")
})

Calling a method on the main thread?

Objective-C

dispatch_async(dispatch_get_main_queue(), ^{
[self doSomething];
});

Swift

DispatchQueue.main.async {
self.doSomething()
}

Legacy Swift

dispatch_async(dispatch_get_main_queue()) {
self.doSomething()
}

GCD to perform task in main thread

No, you do not need to check whether you’re already on the main thread. By dispatching the block to the main queue, you’re just scheduling the block to be executed serially on the main thread, which happens when the corresponding run loop is run.

If you already are on the main thread, the behaviour is the same: the block is scheduled, and executed when the run loop of the main thread is run.

Block in sync method on background queue is executed on main thread

I believe you’re misreading that comment in the header. It’s not a question of whether you’re dispatching from the main queue, but rather if you’re dispatching to the main queue.

So, here is the well known sync optimization where the dispatched block will run on the current thread:

let backgroundQueue = DispatchQueue(label: "internalqueue", attributes: .concurrent)

// We'll dispatch from main thread _to_ background queue

func dispatchingToBackgroundQueue() {
backgroundQueue.sync {
print(#function, "this sync will run on the current thread, namely the main thread; isMainThread =", Thread.isMainThread)
}
backgroundQueue.async {
print(#function, "but this async will run on the background queue's thread; isMainThread =", Thread.isMainThread)
}
}

When you use sync, you’re telling GCD “hey, have this thread wait until the other thread runs this block of code”. So, GCD is smart enough to figure out “well, if this thread is going to not do anything while I’m waiting for the block of code to run, I might as well run it here if I can, and save the costly context switch to another thread.”

But in the following scenario, we’re doing something on some background queue and want to dispatch it back to the main queue. In this case, GCD will not do the aforementioned optimization, but rather will always run the task dispatched to the main queue on the main queue:

// but this time, we'll dispatch from background queue _to_ the main queue

func dispatchingToTheMainQueue() {
backgroundQueue.async {
DispatchQueue.main.sync {
print(#function, "even though it’s sync, this will still run on the main thread; isMainThread =", Thread.isMainThread)
}
DispatchQueue.main.async {
print(#function, "needless to say, this async will run on the main thread; isMainThread =", Thread.isMainThread)
}
}
}

It does this because there are certain things that must run on the main queue (such as UI updates), and if you’re dispatching it to the main queue, it will always honor that request, and not try to do any optimization to avoid context switches.


Let’s consider a more practical example of the latter scenario.

func performRequest(_ url: URL) {
URLSession.shared.dataTask(with: url) { data, _, _ in
DispatchQueue.main.sync {
// we're guaranteed that this actually will run on the main thread
// even though we used `sync`
}
}
}

Now, generally we’d use async when dispatching back to the main queue, but the comment in the sync header documentation is just letting us know that this task dispatched back to the main queue using sync will actually run on the main queue, not on URLSession’s background queue as you might otherwise fear.

How can we know which Thread is used to execute Task?

UIViewController is marked @MainActor which means that tasks will be dispatched on the main thread. Your X class is not marked @MainActor so tasks are dispatched on any available thread.

Swift iOS -Difference in calling a function on the main queue from what's inside the function on the main queue?

As a rule you should update the UI components only on main thread. Hence

DispatchQueue.main.async { [weak self] in
self?.recordButton.isEnabled = true
}

Makes absolute sense. Because you would like the button to be enabled and it is a UI modification you would want it to be in main thread. So all the statements in the closure of DispatchQueue.main.async will be executed on main thread.

Where as in

DispatchQueue.main.async { [weak self] in
self?.enableRecordButton()
}

The method enableRecordButton will be executed on main thread. That means all the statements in enableRecordButton method and all the subsequent methods it calls will be executed on main thread.

In your case, if all that you wanna achieve is enabling a button than putting it in a separate function would not make much sense. Only benefit that you get is that you would be able to enable the button by calling enableRecordButton function from wherever you want rather than repeating same statement again n again.

Finally

... completionHandler{(

DispatchQueue.main.async { [weak self] in
self?.enableRecordButton()
}
)}

func enableRecordButton(){
DispatchQueue.main.async { [weak self] in
self?.recordButton.isEnabled = true
}
}

This in your case makes no sense. Statement

    DispatchQueue.main.async { [weak self] in
self?.enableRecordButton()
}

will already ensure that all statements in enableRecordButton would execute on main thread so adding DispatchQueue.main.async in enableRecordButton makes no sense.

func enableRecordButton(){
DispatchQueue.main.async { [weak self] in
self?.recordButton.isEnabled = true
}
}

This might come handy only if you have multiple points in your code from where you might call enableRecordButton and might call it on non-main thread in that case you can safely remove

    DispatchQueue.main.async { [weak self] in
self?.enableRecordButton()
}

and simply call self?.enableRecordButton() in your completion handler and DispatchQueue.main.async in enableRecordButton function will ensure that all the statements will be executed on main thread.

Hope it helps

Swift DispatchQueue global and main in variable

The key issue is to ensure thread-safety. For example, the following is not thread-safe:

func addUpValuesNotThreadSafe() {
var total = 0

getMyFirstItem { value in
total += value // on main thread
}

getMySecondItem { value in
total += value // on some GCD worker thread!!!
}

getMyThirdItem { value in
total += value // on main thread
}

...
}

One could solve this problem by not allowing these tasks run in parallel, but you lose all the benefits of asynchronous processes and the concurrency they offer.

Needless to say, when you do allow them to run in parallel, you would likely add some mechanism (such as dispatch groups) to know when all of these asynchronous tasks are done. But I did not want to complicate this example, but rather keep our focus on the thread-safety issue. (I show how to use dispatch groups later in this answer.)

Anyway, if you have closures called from multiple threads, you must not increment the same total without adding some synchronization. You could add synchronization with a serial dispatch queue, for example:

func addUpValues() {
var total = 0
let queue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".synchronized")

getMyFirstItem { value in
queue.async {
total += value // on serial queue
}
}

getMySecondItem { value in
queue.async {
total += value // on serial queue
}
}

getMyThirdItem { value in
queue.async {
total += value // on serial queue
}
}

...
}

There are a variety of alternative synchronization mechanisms (locks, GCD reader-writer, actor, etc.). But I start with the serial queue example to observe that, actually, any serial queue would accomplish the same thing. Many use the main queue (which is a serial queue) for this sort of trivial synchronization where the performance impact is negligible, such as in this example.

For example, one could therefore either refactor getMySecondItem to also call its completion handler on the main queue, like getMyFirstItem and getMyThirdItem already do. Or if you cannot do that, you could simply have the getMySecondItem caller dispatch the code that needs to be synchronized to the main queue:

func addUpValues() {
var total = 0

getMyFirstItem { value in
total += value // on main thread
}

getMySecondItem { value in
DispatchQueue.main.async {
total += value // now on main thread, too
}
}

getMyThirdItem { value in
total += value // on main thread
}

// ...
}

That is also thread-safe. This is why many libraries will ensure that all of their completion handlers are called on the main thread, as it minimizes the amount of time the app developer needs to manually synchronize values.


While I have illustrated the use of serial dispatch queues for synchronization, there are a multitude of alternatives. E.g., one might use locks or GCD reader-writer pattern.

The key is that one should never mutate a variable from multiple threads without some synchronization.


Above I mention that you need to know when the three asynchronous tasks are done. You can use a DispatchGroup, e.g.:

func addUpValues(complete: @escaping (Int) -> Void) {
let total = Synchronized(0)
let group = DispatchGroup()

group.enter()
getMyFirstItem { first in
total.synchronized { value in
value += first
}
group.leave()
}

group.enter()
getMySecondItem { second in
total.synchronized { value in
value += second
}
group.leave()
}

group.enter()
getMyThirdItem { third in
total.synchronized { value in
value += third
}
group.leave()
}

group.notify(queue: .main) {
let value = total.synchronized { $0 }
complete(value)
}
}

And in this example, I abstracted the synchronization details out of addUpValues:

class Synchronized<T> {
private var value: T
private let lock = NSLock()

init(_ value: T) {
self.value = value
}

func synchronized<U>(block: (inout T) throws -> U) rethrows -> U {
lock.lock()
defer { lock.unlock() }
return try block(&value)
}
}

Obviously, use whatever synchronization mechanism you want (e.g., GCD or os_unfair_lock or whatever).

But the idea is that in the GCD world, dispatch groups can notify you when a series of asynchronous tasks are done.


I know that this was a GCD question, but for the sake of completeness, the Swift concurrency async-await pattern renders much of this moot.

func getMyFirstItem() async -> Int {
return 10
}

func getMySecondItem() async -> Int {
await Task.detached(priority: .background) {
return 10
}.value
}

func getMyThirdItem() async -> Int {
return 10
}

func addUpValues() {
Task {
async let value1 = getMyFirstItem()
async let value2 = getMySecondItem()
async let value3 = getMyThirdItem()
let total = await value1 + value2 + value3
print(total)
}
}

Or, if your async methods were updating some shared property, you would use an actor to synchronize access. See Protect mutable state with Swift actors.



Related Topics



Leave a reply



Submit