0

Here is the case:

There are more than 3 async tasks. Each one can only be executed after the completion of the previous one. Yes, must be after the completion of the previous one. That's why dispatch group might not be good for this case because it doesn't care about the 'ordering' quite much. What I'm looking for is a good way of writing such code without too much nesting - just similar to what 'promisekit' does to break the nested block.

I also saw some people suggesting 'serial queue'. Well I tried using the code below:

- (void) asyncMethod1WithCompletion: (void(^)())completion
{
    int64_t delayInSeconds = 5.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        NSLog(@"async 1 finished");
        completion();
    });
}

- (void) asyncMethod2WithCompletion: (void(^)())completion
{
    int64_t delayInSeconds = 2.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        NSLog(@"async 2 finished");
        completion();
    });
}

And I call those methods using serial queue:

dispatch_queue_t serialQueue = dispatch_queue_create("testqueue", DISPATCH_QUEUE_SERIAL);

dispatch_sync(serialQueue, ^{
    NSLog(@"async1 started");
   [self asyncMethod1WithCompletion:^{

   }];
});

dispatch_sync(serialQueue, ^{
    NSLog(@"async2 started");
    [self asyncMethod2WithCompletion:^{

    }];
});

If everything works well, async task 2 should start executing after 5 seconds which is the completion time of async task one

However, the result is not as what I expected:

2016-09-14 18:59:39.853 SerialQueueTest[32002:2292385] async1 started

2016-09-14 18:59:39.854 SerialQueueTest[32002:2292385] async2 started

2016-09-14 18:59:41.854 SerialQueueTest[32002:2292385] async 2 finished

2016-09-14 18:59:45.353 SerialQueueTest[32002:2292385] async 1 finished

Did I write the code wrong? Is there any native way of writing lesser nesting code? If there are 5-6 async tasks and each relies on the result of each other, the code would be very massy. I would even think of one very case: a common login function which also integrates some 3rd party accounts login.

I'm also thinking of using dispatch group in a different way: 1. enter the group and finish the first async task and exit group. 2. inside the dispatch_group_notify block, enter the dispatch group again for second async task. 3. Outside the disptach_group_notify block, calling the second async task and exit the group again. 4. write the second dispatch_group_notify for notifying the completion of the second task.

fans3210
  • 209
  • 2
  • 9
  • The serial queue doesn't help you because you are executing asynchronous operations inside the closures, so that frees the serial queue to execute the next task. It would work if your operations were blocking/synchronous. You can use NSOperationQueue with a dispatch_group inside the NSOperation to block the operation queue until the asynchronous task is complete. – Paulw11 Sep 14 '16 at 14:06
  • @Paulw11, that's the answer I found from stackoverflow. You are right it will definately work if those operations were synchronous. I also saw some solutions which use semaphore. But I think it makes things more complicated. – fans3210 Sep 14 '16 at 14:36
  • You can use dispatch groups to effectively make your asynchronous tasks synchronous as per the answer below – Paulw11 Sep 14 '16 at 14:44

2 Answers2

0
- (dispatch_group_t) asyncMethod1
{
    NSLog(@"async1 started");
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_enter(group);
    int64_t delayInSeconds = 5.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        NSLog(@"async 1 finished");
        dispatch_group_leave(group);
    });
    return group;
}

- (dispatch_group_t) asyncMethod2
{
    NSLog(@"async2 started");
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_enter(group);
    int64_t delayInSeconds = 2.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        NSLog(@"async 2 finished");
        dispatch_group_leave(group);
    });
    return group;
}

And using:

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

dispatch_async(queue, ^{
   dispatch_group_notify([self asyncMethod1], queue, ^{
      [self asyncMethod2];
   });
});

dispatch_group_t looks similar there like promise. As you can see, you can even use concurrent queue.

Cy-4AH
  • 4,370
  • 2
  • 15
  • 22
0

You can use NSOperationQueue here.

  1. Create n block operations(NSBlockOperation), where n is the number of async operations you have.
  2. Add them in the NSOperationQueue in the order you want them to be executed in.
  3. Set maxConcurrentOperationCount of NSOperationQueue to 1 to make the queue as serial.
Anup H
  • 549
  • 7
  • 10