2

I have two different big tasks with several subtasks each. Each subtask has a child NSProgress which I update manually. Each big task hosts a parent NSProgress with several

[progress becomeCurrentWithPendingUnitCount:1.0]
// Perform subtask which generates the child `NSProgress`.
[progress resignCurrent]

calls at different times. Each of the two big task progress reporting works fine with this setup.

My problem is that I want to execute these two big tasks in parallel, and I want to track their progress as a whole. Is there any way to do this?

I have tried creating a NSProgress object at the outer level to wrap the two big task's NSProgress (so three NSProgress levels in total), but the problem is that the two tasks "fight" when updating progress. That is, one task could execute becomeCurrentWithPendingUnitCount: and then the other task could execute resignCurrent which causes an exception (since the second task's NSProgress is not current).

Making the two tasks sequential instead of parallel fixes the issue, but I really would like to execute them at the same time. Any ideas?

Ricardo Sanchez-Saez
  • 9,466
  • 8
  • 53
  • 92

1 Answers1

3

Yes you can run 2 async operations in parallel with an NSProgress. Here's a snippet of what I do.

   dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        [self.progress becomeCurrentWithPendingUnitCount:40];
        [self startAsyncTask1];
        [self.progress resignCurrent];
        [self.progress becomeCurrentWithPendingUnitCount:60];
        [self startAsyncTask2];
        [self.progress resignCurrent];
    });

- (void)startAsyncTask1{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        NSProgress *taskProgress = [NSProgress progressWithTotalUnitCount:allUsers.count];
        [taskProgress becomeCurrentWithPendingUnitCount:allUsers.count];
        [self startUploadingAllUsers];
        [taskProgress resignCurrent];
    });
}
-(void)startUploadingAllUsers{
    for(id user in allUsers){
        [self uploadUser:user];
    }
}
-(void)uploadUser:(id)user{
    NSProgress *taskProgress = [NSProgress progressWithTotalUnitCount:user.photos.count];
    //Do upload and in completion of photo upload increment taskProgress.completedUnitCount
    //This last task does not get resigned. It does not become active either.
}

You have to make sure your 2 tasks are asynchronous. That means the resign call is called right away, even though the task is still executing. When I tried to resign in the completion of an async task, I got that error due to the fact that the NSProgress had already been resigned elsewhere. So Like my example Task1 and Task2 are also async and also contain child NSProgresses in them. You more or less want to resign the current progress right after kicking off the async task and not waiting until it is done.

As a side note I like to use 100 units as the pending so I can think of each task as a percentage of it. You could also use byte count for units pending but I tend to do that at the lower child processes where actual data processing happens and not in the parent process. Like my example I dispatch an async task for uploading all new User objects to an API and a process for uploading all new user photos. The photo task counts the byte size of the photo and uses that for updating the child process but the parent task is about 40% of the main task and that's why I use percentages because some times you don't know the aggregated byte count if you have complex multiple object processes.

Alex Reynolds
  • 6,264
  • 4
  • 26
  • 42
  • Do your tasks' child `NSProgress` also call `becomeCurrentWithPendingUnitCount:` and `resignCurrent`? My problem is that mine do (these aggregate tasks have their own `NSProgress`, and then further children `NSProgress`), so the `becomeCurrentWithPendingUnitCount:` and `resignCurrent` calls in the tasks get called out or order. I figure this wouldn't be a problem for you if your tasks just use leaf `NSProgress` (two levels of `NSProgress` children instead of three). – Ricardo Sanchez-Saez Jul 06 '15 at 19:58
  • So my example for `startAsyncTask1` method creates a local `NSProgress` and say I have 10 users to sync so it sets the total units to 10 and becomes current with 10 pending. It calls starts the api calls that are async and resigns before any of those async calls come back. Inside those API calls I create an `NSProgess` for each call BUT THE LOWEST LEVEL PROGRESS SHOULD NOT RESIGN. so in the API call I never resign that progress I only update completed units. – Alex Reynolds Jul 06 '15 at 20:04
  • I updated the answer with more code. You will see the final child task does not become active and does not get resigned. My real world code is a task that uploads all local objects, uploads all locally created photos and downloads all new objects from a server. So I create a child progress for each Object type like 1 for a Users task, 1 for a photos task all the way down to the individual API calls. Mine is 3 levels deep of `NSProgress`. – Alex Reynolds Jul 06 '15 at 20:15
  • Ah, I see how you do it. The problem in my case is that this big tasks have some async subtasks that need to be executed in sequence (hence, the `NSProgress` for the late subtasks are only created later). That's what causes my problem regarding the two big tasks "fighting". In your case, it seems that you start all the uploads at the same time (and thus, all your `NSProgress` hierarchy is created at the start of the compound operation). – Ricardo Sanchez-Saez Jul 07 '15 at 16:45
  • Also, I think that you have one needless `dispatch_async` call in your sample. `startAsyncTask1` its already being called from the background queue. – Ricardo Sanchez-Saez Jul 07 '15 at 16:50
  • Actually in the real world I am doing what you do. I have a serial queue that uses semaphore locks so that when I add 4 async tasks it will execute in order. It also uses a dispatch group so that once the queue is empty then all the async subtasks are complete. I use the queue to throttle my API calls so that if I have 100 objects to sync I am doing it in serial so I don't hit the network with 100 api calls at once. – Alex Reynolds Jul 07 '15 at 17:23
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/82633/discussion-between-alex-reynolds-and-ricardo-sanchez-saez). – Alex Reynolds Jul 07 '15 at 17:24