Background Operations on Delphi Android, with Threads and Timers

  

You should never do slow, blocking operations on mobile. You should probably avoid those also on desktop, but on mobile the scenario is worse as your app will own the entire screen. In fact, the Android OS does complain almost immediately if your app is not responsive. To better explain the scenario and show some possible solutions, let me start with some code.
Slow Code, Unresponsive UI
If you’ve read my book, the code won’t be new: let’s compute how many prime numbers are there below a given value. The simplest solution is to compute the value in the OnClick event handler of a button:

var
I: Integer;
Total: Integer;
begin
// counts the prime numbers below the given value
Total := 0;
for I := 1 to MaxValue do
begin
if (I * 10 mod MaxValue) = 0 then
ListView1.Items.Add.Text := ‘B: ‘ + I.ToString;
if IsPrime (I) then
Inc (Total);
end;
ListView1.Items.Add.Text := ‘Blocking: ‘ + Total.ToString;

The display of the status actually doesn’t work because the screen is not refreshed until the end. But that’s not the biggest issue. The issue is that if you run the code with a large enough MaxValue ( I used 200,000) you’ll see the following:

Android sees the app is not responsive and suggests killing it. This is clearly a big issue.
Timer, Do Timer, Do Timer
What is the alternative? There are at least a couple. One is to split the long computation is many shorter ones. This can be achieved using a timer and doing some of the processing for each timer execution, suspend the work, and wait for another timer event. In this scenario, the counter and total value (TimerI and TimerTotal) must be global form variables, and we need to disable the button to avoid re-entrance:

procedure TForm5.Button2Click(Sender: TObject);
begin
TimerI := 1;
TimerTotal := 0;
Button2.Enabled := False;
Timer1.Enabled := True;
end;

procedure TForm5.Timer1Timer(Sender: TObject);
var
I: Integer;
begin
for I := TimerI to TimerI + MaxValue div 10 – 1 do
begin
if (I * 10 mod MaxValue) = 0 then
ListView1.Items.Add.Text := ‘T: ‘ + I.ToString;
if IsPrime (I) then
Inc (TimerTotal);
end;
Inc (TimerI, MaxValue div 10);

if TimerI >= MaxValue then
begin
Button2.Enabled := True;
Timer1.Enabled := False;
ListView1.Items.Add.Text := ‘Timer: ‘ + TimerTotal.ToString;
NotifyComplete;
end;
end;
This time the listview content is updated for each tenth of the calculation, and you should not get the “unresponsive error”. For a slower phone, you might want to device the process in smaller chunks. Now what is interesting is that the timer events will keep being executed and processed even if you hit the home button or bring another app to the foreground. More about that later.
Threading Is It
Using a timer seems nice, but it is really not ideal. If there is too much processing, you might still have a unresponsive application. If you wait too much time, your algorithm will take forever. The issue is that the timer event handler is execute in the context of the main thread, the UI thread, the only thread that processes user interaction. In this respect, Android is not much different than Windows.
That’s why the real solution for background operations that will keep the UI thread responsive is to use a thread. Sounds difficult? Not at all. The anonymous threads supported by the Delphi RTL make this operation really easy. There is one caveat, though. When the thread needs to update the UI it has to “synchronize” with the main, UI thread. This is the code, which follows the same blueprint of the original version:

procedure TForm5.Button3Click(Sender: TObject);
begin
TThread.CreateAnonymousThread(procedure ()
var
I: Integer;
Total: Integer;
begin
Total := 0;
for I := 1 to MaxValue do
begin
if (I * 10 mod MaxValue) = 0 then
TThread.Synchronize (TThread.CurrentThread,
procedure ()
begin
ListView1.Items.Add.Text := ‘Th: ‘ + I.ToString;
end);

if IsPrime (I) then
Inc (Total);
end;

TThread.Synchronize (TThread.CurrentThread,
procedure ()
begin
ListView1.Items.Add.Text := ‘Thread: ‘ + Total.ToString;
NotifyComplete;
end);
end).Start;
end;
By the way, remember to Start the thread you create, or nothing will happen! If you haven’t use recent versions of Delphi you might be surprised, I know. This code uses nested anonymous methods for the main thread function and the synchronization. Using a timers leaves the UI thread free, Ui updates are fast, and everything is smooth:

Again, you can use the home button, start another application, and your app thread will keep working in the background until it finishes computing the prime numbers. Does this mean we can use a thread to keep an application running (like polling external resources) continuously? That is not true. The user can terminate the application at will (although this might not be very common and will be acceptable), but also the operating system might kill the application if the users starts too many apps or apps consuming a lot of memory and CPU. The operating system has the right to terminate “resource hungry” applications. To make sure an Android apps remains in memory indefinitely, you need to write a server, which is a different type of app Delphi doesn’t directly support at this moment.
Hey, User
There is another issue worth considering. Suppose a user starts the process, notices it is taking a lot of time, switches to another application. When will he or she get back? The ideal scenario is to have the application notify the user that the process is completed, and this is why my demo does in the NotifyComplete method called both by the thread-based and the timer-based versions:

procedure TForm5.NotifyComplete;
var
Notification: TNotification;
begin
{ verify if the service is actually supported }
if NotificationCenter1.Supported then
begin
Notification := NotificationCenter1.CreateNotification;
Notification.Name := ‘Background Test’;
Notification.AlertBody := ‘Prime Numbers Computed’;
Notification.FireDate := Now;

{ Send notification in Notification Center }
NotificationCenter1.ScheduleNotification(Notification);
end
end;
This shows a nice notification on your device that a user can select to get back to the application and see the result of the long calculation:

 
In summary: threading in Android with Delphi is quite simple and similar to Windows, safe for the fact that a background app can be closed by the operating system. Second, remember you can use notifications to let the user know of the app progress. What about iOS? That’s for a future blog post.
 

Comments are closed.