Async Tasks in VCL Projects

Sometimes actions inside an application need their time. From retrieving data from REST service or a database to scanning your hard disk for all files containing images, there are a plethora of things that can be time consuming. In case these tasks are executed in the main thread the application will probably become unresponsive and feels like frozen – to the user as well as to the operating system.

There is a good chance that one can solve this by moving the time consuming task into a thread. That is usually where the problems start creeping in.

Let’s pick one of the above scenarios for a concrete example. Searching for files is something we all do often enough to be of practical relevance and it is something everyone can do without any other requirements. It can even be done without an internet connection, although one would have difficulties to actually read this article in that case.

I have created a small VCL project containing an edit to specify the root folder for the search and a search box for the file mask. The found file names will be displayed in a list view. As the list can be pretty long the list view is used in virtual mode.

The relevant methods doing the search are pretty straight forward:

When we start this program and execute a non-trivial search, the app becomes unresponsive until the search is finished. Let’s move the search to a thread now.

Wait!

The first rule when it comes to multi-threaded programming is: Never start with a thread!

Always work in the main thread and refactor the architecture and classes until you feel comfortable to move the code to a thread. Identify the code parts that need synchronization to the main thread. Make sure all data is used by one thread at a time. Think of parallel, but don’t do it. If you think you are ready to add System.Threading (or your preferred library) to the uses clause – continue refactoring for another couple of rounds to also cover the corner cases you missed up to now.

Often it is a good thing to extract the future threading code into its own class and abstract the dependencies to the user interface (in this case the VCL controls). The relevant code parts here are three methods: BeginSearch, EndSearch and AddFiles. We create an interface ISearchTarget for these methods.

Luckily TComponent happens to implement IInterface and as TForm is derived from TComponent it is sufficient to add ISearchTarget to TSearchForm to make it support the interface.

Remember, we are still working for a single-threaded implementation and no async-whatever is in sight here, so we simply name the new class TSearch.

The SearchFolder implementation is the same as in TSearchForm, while BeginSearch, EndSearch and AddFiles just forward the call to FTarget .

In TSearchForm we can remove the SearchFolder method completely and change the implementation of StartSearch to make use of our new class.

The reason for using a property Search in TSearchForm backed up by a field instead of a local variable will become clear later.

Running some tests show a similar experience as before, but that was expected. Make sure that it works as expected, too.

Can we stop this, please?

When it comes to multi-threading we need to think about a way to cancel the search when the form is closed or another search is initiated. Well, strictly speaking we don’t need to actually stop it, but rather make it skip calling the ISearchTarget methods any longer, but that may keep the search using resources without need. For this we add a method Cancel to our class to set an internal flag, which we expose with property Cancelled.

Then we change BeginSearch, EndSearch and AddFiles to check for Cancelled before forwarding the call (i.e. skip calling the ISearchTarget methods).

We also add a break condition in SearchFolder so we don’t waste resources.

How can we test the new Cancel mechanism? Remember that we introduced the property Search for the TSearch instance instead of a local variable, which actually would have done as well? The property allows us to call Search.Cancel inside AddFiles, which provides a way to test cancelling even in a single threaded scenario.

Now we go parallel!

Well, not yet!

The first thing to note when switching to a parallel execution is that we cannot keep the code in TSearchForm.StartSearch. Creating an instance of TSearch, calling Execute and free it is not a valid approach for asynchronous processing. So, what are we actually doing with Search after its creation? We only call Execute and Cancel.

Perhaps we don’t need the full fledged TSearch instance and just a way to call Cancel would do? What about an interface ICancel providing such a method that we can hold in TSearchForm instead of the TSearch instance?

To make TSearch give us such an interface we declare a class method Execute returning the interface as an out parameter. The protected overloaded Execute is for future extension.

We declare a nested class TCancel implementing ICancel, merely just forwarding the calls to the TSearch instance given to its constructor.

Note that TCancel is also responsible for destroying that instance when its reference count goes to zero. This makes the implementation of Execute pretty straight forward.

The declaration and implementation of TSearchForm now look like this.

Setting Search to nil in TSearchForm.StartSearch and TSearchForm.Destroy makes sure that a still running background search will be cancelled and stop calling methods of the soon vanishing form instance or interfering with the new search results.

Why an out parameter instead of a function result?

If TSearch.Execute were a function returning ICancel, the Search property in TSearchForm would be assigned when the function returns. As we still are non-threaded that would be when the search already has finished. Thus we would not have the ICancel interface in Search when we try to call it inside AddFiles. We have already taken care to assign the out parameter before we call instance.Execute.

We can still test all this with our single thread approach.

Now we really go parallel!

We derive TAsyncSearch from TSearch overriding some methods.

To keep ACancel alive during the anonymous method (and thus the current TAsyncSearch instance), we call ACancel.IsCancelled instead of simply Cancelled.

We have to add some synchronization for the codes that access FTarget when we are not in the main thread.

There we are. Searching in the background.

Note the number of lines in this article actually doing parallel execution compared to the number of lines describing the preparation and refactoring.

As a side effect we got a simple class TSearch that can even be used in a separate thread providing a suitable ISearchTarget. Note that TAsyncSearch may not be fit for that, because it starts its own thread and synchronizes with the main thread.

The sources used in this article can be downloaded here: AsyncTasksInVclProjectsSource. (need Delphi 10.4.2 or up)
Update: also available on https://github.com/UweRaabe/AsyncTasksInVclProjects

During my research for this article I found that the incredible Dalija Prasnikar has a similar example in chapter 35.2.2 of her book Delphi Event-based and Asynchronous Programming. You will also find an explanation why calling Cancel and Cancelled from different threads using a Boolean field is safe and what would be not. I can highly recommend that book for everyone thinking of starting multi threaded programming – and especially for those already doing it.

 

Author: Uwe Raabe

Addicted to Pascal/Delphi since the late 70's

One thought on “Async Tasks in VCL Projects”

  1. A nicely written article, with some interesting ideas within. For starters I’d never even thought of calling inherited from within an anonymous method before, threads or no threads.

    I tend to use composition rather than inheritance as my mechanism for threads encapsulating non-threaded code, it’s interesting to see a different approach.

Comments are closed.