打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Background Operations on Delphi Android, with Threads and Timers

Background Operations on Delphi Android, with Threads and Timers

Given you should never do slow, blocking operations on mobile, here are a couple of techniques (mostly threading) that can help keep your apps responsive.


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);  endend;

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.

 


posted by marcocantu @ 9:48AM | 13 Comments [0 Pending]





 

13 Comments

Background Operations on Delphi Android, with Threads and Timers 

 > you need to write a server, which isis it service?

Comment by Tomohiro Takahashi on May 21, 10:12


Background Operations on Delphi Android, with Threads and Timers 

The problem with a time is you are wasting time. Onereally old school method (and i dont mean "that methodis ok") on windows was just anapplication.processmessages() in your loop.When you use an anomynous thread, you should disablethe button or the user hit multiply times on thebutton and you ends up with a lot of threads currentlyworking... 

Comment by mezen [] on May 21, 10:32


Background Operations on Delphi Android, with Threads and Timers 

Tomohiro,   right I meant a service. I'll edit the blog post. Mezen,   yes, I know timer is old school, but wanted to underline that the timer keep firing even if the app is not in the foreground. As for re-executing the thread, it is true this makes little sense, but the code uses thread variables so the execution of multiple calculations at the same time should technically work correctly.-Marco

Comment by Marco Cantu [http://www.marcocantu.com] on May 21, 10:53


Background Operations on Delphi Android, with Threads and Timers 

 Cantu was exactly what I was looking for, thank you very much, but just one more question. In your example it is handled on error and exception?

Comment by Wagner Alexandre on May 21, 12:41


Background Operations on Delphi Android, with Threads and Timers 

 Hi Marco.What is you want to add a progressbar ?like mezen said on windows progressbar would not display without an application.processmessages;No Application.processmessage for application cross platform compatibility ?

Comment by Wchris on May 21, 20:35


Background Operations on Delphi Android, with Threads and Timers 

What is the advantage of the anonymous Thread? Is there a reason not to use a normal Thread on Android?

Comment by Les Kaye on May 22, 06:08


Background Operations on Delphi Android, with Threads and Timers 

 @Wchris:Sorry, this is not correct. You can use a progressbarwith a timer or a thread (if you use Synchronize inyour thread), because your main (ui) thread is notblocked all the time.@Les Kaye:The advantage of an anonymous thread is that you haveto write less code. You could use a normal derivate ofTThread, but this is more code and you have novariable capturing so you have to put the ListView anda Callback to NotifiyComplete() in.

Comment by mezen on June 1, 14:31


Background Operations on Delphi Android, with Threads and Timers 

How would I code the equivalent of this example in C++?

Comment by PlusPlus on June 6, 08:09


Background Operations on Delphi Android, with Threads and Timers 

 Is there the same for ios??? Download a file and examine the content every x minutes when the app si in background??

Comment by Davide on June 8, 21:33


Background Operations on Delphi Android, with Threads and Timers 

 Dear Marco,I have tried you threading example and it works great for UI related functions. What I need it for, however, is to call a WCF (.net) web service function, which always runs long (over 20 seconds sometimes!). I want to display a AniIndicator while the download is in progress.When I incorporate my Service function call into a Anon Thread, it simply does not run. At least, the thread never reaches it ends, meaning that the AniIndicator now displays indefinitely. Here is the code that needs to perform this action. Did I do something wrong?procedure TfrmMain.Button1Click(Sender: TObject);const MaxValue : Integer = 2000;begin  AniIndicator1.Visible := True;  Svc := Service.GetIService(false);  Button1.Enabled := False;  Button1.Text := 'Start Process (working...)';  TThread.CreateAnonymousThread(procedure ()    var      i : Integer;      StartTime : TTime;      EndTime : TTime;      Hr, Mins, Sec, mSec : Word;      Total : Integer;    begin      try        StartTime := Now;        Total := 0;        Svc := Service.GetIService(false);        AllCities := Svc.GetCountryCities(1);        Listview1.ClearItems;        if AllCities.Len > 0 then          begin            for i := 0 to AllCities.Len - 1 do              begin                TThread.Synchronize (TThread.CurrentThread,                procedure ()                begin                  ListView1.Items.Add.Text := AllCities[i].CityName;                  Total := Total + 1;                end);              end;            EndTime := Now;            DecodeTime(EndTime - StartTime, Hr, Mins, Sec, mSec);            //ShowMessage('It took ' + IntToStr(mSec) + ' milliseconds to download the ' + IntToStr(AllLoads.Len) + ' Loads!');          end        else          begin            //ShowMessage('Could not download any Loads!');          end;      except         TThread.Synchronize (TThread.CurrentThread,          procedure ()          begin            ShowMessage('There was an error connecting to the server!');            Button1.Enabled := True;          end);      end;      TThread.Synchronize (TThread.CurrentThread,        procedure ()        begin          ListView1.Items.Add.Text := 'Cities: ' + Total.ToString;          //NotifyComplete;          AniIndicator1.Visible := False;          Button1.Enabled := False;        end);    end).Start;end;

Comment by Hendrik Potgieter [http://www.deezinetech.co.za] on August 4, 07:15


Background Operations on Delphi Android, with Threads and Timers 

Thank you, this helped allot! I just added a simple check procedure with a variable to see if the thread is still in process or not, so the user doesn't start another thread.   

Comment by Simon on August 18, 10:08


Background Operations on Delphi Android, with Threads and Timers 

XE6 C++ BuilderI have developed a Client (Android App) and it uses TThread for the TCP connection read/write and data input in form (through Synchronize). Problem occures during the test when I switch off the WiFi receiver - thread simply terminates itself without any reason (all other form object are still "alive"). Why do it happen?

Comment by Rolands on September 15, 22:09


Background Operations on Delphi Android, with Threads and Timers 

 Hello, Good example, I have question to use paralel library.Your code with TTask:procedure TForm5.Button3Click(Sender: TObject);begin  TTask.Run(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);end; Is this code corectly? Is this code same for androidlike yours? Do you have experience what is better?Thanks.Alex.

Comment by AlexB on November 10, 13:26

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
Delphi多线程编程-南山古桃-关键词:Delphi,线程,多线程,详解,TThread...
Assertion failure: "(!"SetThreadContext failed")"问题的解决办法
delphi怎样编写服务程序-Service Application,编好了怎么安装这个服...
Android Java Framework显示Toast(无Activity和Servi...
Android中Service与IntentService的使用比较
Android开发实践:使用Service还是Thread
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服