venerdì 3 ottobre 2014

Background threads

Studying background threads

These days FitPlot 5.1 is under the lens of the MAS examiners to be finally (hoping) approved.
In the meantime I want to write down, so I'll remember when needed, about the try to use a background thread.



I do not like the rainbow spinning cursor in my app. The user loose its control and have just to wait the end of the operation.
This usually appears in loops (for cycles) and the only way to avoid the user to panic (and ctrl-alt-esc the program), is to show the progress of the operation with a progress bar or a with a field showing what is happening.

This is what I have tried to do in the latest added feature in imposition (see  2-Up Saddle Stitch update).


The problem

I had a - loop in a loop - situation and a progress bar showing only the inner loop.
For example, in a 120 pages imposition, the pages grouped in 12 pages booklets, I had a first for cycle (from 1 to 10) that at each loop called the imposition procedure of the first 12 pages and so on.
The imposition procedure has its own loop showing a progress bar (from 1 to 12 in this case).
What the user was seeing while running was the rainbow spinning wheel and a progress bar going from 0 to 12 for twelve times.
The user knows that the program is not idle, but he does not have idea unless it counts the number of times the progress bar reaches the end.
So first idea was to couple the progress bar with a field showing the first loop cycle value (1/12 to 12/12 in this case).
I prepared the NIB, the necessary outlet was connected, program ok, but the field does not changed until the end of the loop taking suddenly the 12/12 value. Why?
NSLog testified that the values was changing in the loop but the field value was not updated until the end.


The answer

A search on the web led me to this discussion:

http://stackoverflow.com/questions/343088/nstextview-not-updating-in-a-loop-while-writing-to-a-usb-device

Where Chris Hanson answered textually:
"Mac OS X does not flush changes to views to the screen immediately, to avoid the flicker and tearing that's common in such situations. This means you can't just sit in a loop and perform blocking operations while you update a view; if you do this, neither the view nor anything else in your application's human interface will update, and your application will appear hung and eventually get the spinning cursor."

That was the problem! The next step was to use a background thread (as suggested) while updating the field on the main thread.


Implementation

Implementation of background threads it is straightforward:


            __block BOOL redraw = NO;

            dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                
                for (int i = 0; i < numeroCicli; i++)
                {
                    // Update the text field on the main queue
                    dispatch_async(dispatch_get_main_queue(), ^{
                    //Code here to which needs to update the UI in the UI thread goes here
                    [progressFiled setStringValue setStringValue: [NSString stringWithFormat:@"%i / %i",i+1, numeroCicli];

                    });
                    
                  
                    [self impose:sender
                             pdf:elem
                   startingFromY:newImpoSize.height*i*[groupImpo selectedTag]/[pagineFormatoImpo selectedTag]*2
                          redraw:redraw];
                    
                    //NSLog(@"giro %i di %i, da: %i a: %i", i, (int)numeroCicli, (int)[rangeImpoDa integerValue], (int)[rangeImpoA integerValue]);
                }
            });

Put your loop in the bracketed block, leave a dispatch in the main queue to update the field in the same bracketed way. Just remember to  __block needed variables declared outside the block and it is done.


Results

The result is that the spinning rainbow cursor disappear, the field is updated and the user maintains the control throughout all the time of the operation.


What? Has he the control?
Yes!
Can he change, move, delete objects?
Yes! What's wrong?
What's wrong? He can delete an object while another thread is using this same object!
This is utterly dangerous!


And the winner is…

At the end I learned a new powerful programming technique I didn't know, but I am not using it.
Instead I have, for this particular doble loop case, two independent progress bar, the first counting the cycles in the first loop and the second one for the second loop.
NSProgressBar bar are updated regularly while in a loop, where NSTextField is not.

You will see it in FitPlot 5.1.0, soon, I hope.