[ios] Does dispatch_async(dispatch_get_main_queue(), ^{...}); wait until done?

I have a scenario in my app, where I want to do some time consuming task which consists of some data processing as well as UI update, in a method. My method looks like this,

- (void)doCalculationsAndUpdateUIs {

    // DATA PROCESSING 1
    // UI UPDATE 1

    // DATA PROCESSING 2
    // UI UPDATE 2

    // DATA PROCESSING 3
    // UI UPDATE 3
} 

As it is time consuming I wanted to do the data processing on the background thread, using,

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{

But as both data processing and UI updates are in the same method, I wanted to move only the UI updates in main thread using,

dispatch_async(dispatch_get_main_queue(), ^{

Finally my method looks like this,

- (void)doCalculationsAndUpdateUIs {

    // DATA PROCESSING 1 
    dispatch_async(dispatch_get_main_queue(), ^{
        // UI UPDATE 1
    });

    /* I expect the control to come here after UI UPDATE 1 */

    // DATA PROCESSING 2
    dispatch_async(dispatch_get_main_queue(), ^{
        // UI UPDATE 2
    });

    /* I expect the control to come here after UI UPDATE 2 */

    // DATA PROCESSING 3
    dispatch_async(dispatch_get_main_queue(), ^{
        // UI UPDATE 3
    });
}

Does this really work? Is this really a good practice? What is the best way to achieve this?

P.S. All these three operations are interrelated to each other.


EDIT: Sorry guys. I have missed a line in the above code. My actual code looks like this.

- (void)doCalculationsAndUpdateUIs {

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        // DATA PROCESSING 1 
        dispatch_async(dispatch_get_main_queue(), ^{
            // UI UPDATE 1
        });

        /* I expect the control to come here after UI UPDATE 1 */

        // DATA PROCESSING 2
        dispatch_async(dispatch_get_main_queue(), ^{
            // UI UPDATE 2
        });

        /* I expect the control to come here after UI UPDATE 2 */

        // DATA PROCESSING 3
        dispatch_async(dispatch_get_main_queue(), ^{
            // UI UPDATE 3
        });
    });
}

Once again, I really apologize for the confusion.

The answer is


OK, there are two ways of doing that:

// GLOBAL_CONCURRENT_QUEUE


- (void)doCalculationsAndUpdateUIsWith_GlobalQUEUE 
{
    dispatch_queue_t globalConcurrentQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(globalConcurrentQ, ^{

       // DATA PROCESSING 1
       sleep(1);
       NSLog(@"Hello world chekpoint 1");
       dispatch_sync(dispatch_get_main_queue(), ^{
           // UI UPDATION 1
           sleep(1);
           NSLog(@"Hello world chekpoint 2");
       });

        /* the control to come here after UI UPDATION 1 */
        sleep(1);
        NSLog(@"Hello world chekpoint 3");
        // DATA PROCESSING 2

        dispatch_sync(dispatch_get_main_queue(), ^{
            // UI UPDATION 2
            sleep(1);
            NSLog(@"Hello world chekpoint 4");
        });

        /* the control to come here after UI UPDATION 2 */
        sleep(1);
        NSLog(@"Hello world chekpoint 5");
        // DATA PROCESSING 3

        dispatch_sync(dispatch_get_main_queue(), ^{
            // UI UPDATION 3
            sleep(1);
            NSLog(@"Hello world chekpoint 6");
        });
   });
}



// SERIAL QUEUE
- (void)doCalculationsAndUpdateUIsWith_GlobalQUEUE 
{

    dispatch_queue_t serialQ = dispatch_queue_create("com.example.MyQueue", NULL);
    dispatch_async(serialQ, ^{

       // DATA PROCESSING 1
       sleep(1);
       NSLog(@"Hello world chekpoint 1");

       dispatch_sync(dispatch_get_main_queue(), ^{
           // UI UPDATION 1
           sleep(1);
           NSLog(@"Hello world chekpoint 2");
       });


       sleep(1);
       NSLog(@"Hello world chekpoint 3");
       // DATA PROCESSING 2

       dispatch_sync(dispatch_get_main_queue(), ^{
           // UI UPDATION 2
           sleep(1);
           NSLog(@"Hello world chekpoint 4");
       });  
   });
}

The good practice is: Dispatch Groups

dispatch_group_t imageGroup = dispatch_group_create();

dispatch_group_enter(imageGroup);
[uploadImage executeWithCompletion:^(NSURL *result, NSError* error){
    // Image successfully uploaded to S3
    dispatch_group_leave(imageGroup);
}];

dispatch_group_enter(imageGroup);
[setImage executeWithCompletion:^(NSURL *result, NSError* error){
    // Image url updated
    dispatch_group_leave(imageGroup);
}];

dispatch_group_notify(imageGroup,dispatch_get_main_queue(),^{
    // We get here when both tasks are completed
});

If you want to run a single independent queued operation and you’re not concerned with other concurrent operations, you can use the global concurrent queue:

dispatch_queue_t globalConcurrentQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

This will return a concurrent queue with the given priority as outlined in the documentation:

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).


dispatch_queue_t queue = dispatch_queue_create("com.example.MyQueue", NULL);
dispatch_async(queue, ^{
  // Do some computation here.

  // Update UI after computation.
  dispatch_async(dispatch_get_main_queue(), ^{
    // Update the UI on the main thread.
  });
});

No, it won't wait.

You could use performSelectorOnMainThread:withObject:waitUntilDone:.


You have to put your main queue dispatching in the block that runs the computation. For example (here I create a dispatch queue and don't use a global one):

dispatch_queue_t queue = dispatch_queue_create("com.example.MyQueue", NULL);
dispatch_async(queue, ^{
  // Do some computation here.

  // Update UI after computation.
  dispatch_async(dispatch_get_main_queue(), ^{
    // Update the UI on the main thread.
  });
});

Of course, if you create a queue don't forget to dispatch_release if you're targeting an iOS version before 6.0.


Your proposed doCalculationsAndUpdateUIs does data processing and dispatches UI updates to the main queue. I presume that you have dispatched doCalculationsAndUpdateUIs to a background queue when you first called it.

While technically fine, that's a little fragile, contingent upon your remembering to dispatch it to the background every time you call it: I would, instead, suggest that you do your dispatch to the background and dispatch back to the main queue from within the same method, as it makes the logic unambiguous and more robust, etc.

Thus it might look like:

- (void)doCalculationsAndUpdateUIs {

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{

        // DATA PROCESSING 1 

        dispatch_async(dispatch_get_main_queue(), ^{
            // UI UPDATION 1
        });

        /* I expect the control to come here after UI UPDATION 1 */

        // DATA PROCESSING 2

        dispatch_async(dispatch_get_main_queue(), ^{
            // UI UPDATION 2
        });

        /* I expect the control to come here after UI UPDATION 2 */

        // DATA PROCESSING 3

        dispatch_async(dispatch_get_main_queue(), ^{
            // UI UPDATION 3
        });
    });
}

In terms of whether you dispatch your UI updates asynchronously with dispatch_async (where the background process will not wait for the UI update) or synchronously with dispatch_sync (where it will wait for the UI update), the question is why would you want to do it synchronously: Do you really want to slow down the background process as it waits for the UI update, or would you like the background process to carry on while the UI update takes place.

Generally you would dispatch the UI update asynchronously with dispatch_async as you've used in your original question. Yes, there certainly are special circumstances where you need to dispatch code synchronously (e.g. you're synchronizing the updates to some class property by performing all updates to it on the main queue), but more often than not, you just dispatch the UI update asynchronously and carry on. Dispatching code synchronously can cause problems (e.g. deadlocks) if done sloppily, so my general counsel is that you should probably only dispatch UI updates synchronously if there is some compelling need to do so, otherwise you should design your solution so you can dispatch them asynchronously.


In answer to your question as to whether this is the "best way to achieve this", it's hard for us to say without knowing more about the business problem being solved. For example, if you might be calling this doCalculationsAndUpdateUIs multiple times, I might be inclined to use my own serial queue rather than a concurrent global queue, in order to ensure that these don't step over each other. Or if you might need the ability to cancel this doCalculationsAndUpdateUIs when the user dismisses the scene or calls the method again, then I might be inclined to use a operation queue which offers cancelation capabilities. It depends entirely upon what you're trying to achieve.

But, in general, the pattern of asynchronously dispatching a complicated task to a background queue and then asynchronously dispatching the UI update back to the main queue is very common.


Examples related to ios

Adding a UISegmentedControl to UITableView Crop image to specified size and picture location Undefined Symbols error when integrating Apptentive iOS SDK via Cocoapods Keep placeholder text in UITextField on input in IOS Accessing AppDelegate from framework? Autoresize View When SubViews are Added Warp \ bend effect on a UIView? Speech input for visually impaired users without the need to tap the screen make UITableViewCell selectable only while editing Xcode 12, building for iOS Simulator, but linking in object file built for iOS, for architecture arm64

Examples related to objective-c

Adding a UISegmentedControl to UITableView Keep placeholder text in UITextField on input in IOS Accessing AppDelegate from framework? Warp \ bend effect on a UIView? Use NSInteger as array index Detect if the device is iPhone X Linker Command failed with exit code 1 (use -v to see invocation), Xcode 8, Swift 3 ITSAppUsesNonExemptEncryption export compliance while internal testing? How to enable back/left swipe gesture in UINavigationController after setting leftBarButtonItem? Change status bar text color to light in iOS 9 with Objective-C

Examples related to multithreading

How can compare-and-swap be used for a wait-free mutual exclusion for any shared data structure? Waiting until the task finishes What is the difference between Task.Run() and Task.Factory.StartNew() Why is setState in reactjs Async instead of Sync? What exactly is std::atomic? Calling async method on button click WAITING at sun.misc.Unsafe.park(Native Method) How to use background thread in swift? What is the use of static synchronized method in java? Locking pattern for proper use of .NET MemoryCache

Examples related to grand-central-dispatch

Waiting until the task finishes How to create dispatch queue in Swift 3 How do I write dispatch_after GCD in Swift 3, 4, and 5? How do I dispatch_sync, dispatch_async, dispatch_after, etc in Swift 3, Swift 4, and beyond? In Swift how to call method with parameters on GCD main thread? EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) on dispatch_semaphore_dispose dispatch_after - GCD in Swift? Does dispatch_async(dispatch_get_main_queue(), ^{...}); wait until done? Waiting until two async blocks are executed before starting another block NSOperation vs Grand Central Dispatch

Examples related to dispatch-async

How do I dispatch_sync, dispatch_async, dispatch_after, etc in Swift 3, Swift 4, and beyond? Does dispatch_async(dispatch_get_main_queue(), ^{...}); wait until done? Understanding dispatch_async iPhone - Grand Central Dispatch main thread