Introduction

Secondary threads

AxIThread can control up to eight independent processes running in the background while your application performs another task. Each process will be executed in an independent thread, being your program the responsible of administering its functionality.

You may use AxIThread functions to control all the possible actions (creation, removal, priority change, etc.) and an external event where to write the code to execute (in VB you'll see it as TheadFunc(nThread as integer), where nThread will be the thread that launches the event) .

You can create threads with any priority level (within the permitted) or modify its priority later assigning a lower level to less relevant tasks and higher priority to the most critical. The more threads you create, the less processing time for each, so you can control at any moment the relation between speed and number of simultaneous tasks as needed, creating threads, removing them or suspending their execution to give more processing time to the others.

And all, taking into account that the thread with higher priority will have privileged processing time. Windooze assigns processing time to every thread running (your program's, other program's and systems ones) giving privileged treatment to the application in the foreground and to those threads with an especial priority. The system recognizes 32 levels of priority, from the highest (level 31) to the lowest (level 0), combining class and level of priority. As a general rule, the base priority of any process is, by default, of level 8. 

AxIThread allows you to modify your thread's priority within five levels (6 to 10). Supposed that your application's primary thread shall have this base priority of level 8 and that threads are supposed to run background tasks, giving a priority of 9 or 10 to some of them at the same time will give preference to them over your application so, if the process assigned to those threads overloads the system, the application, the user interface, would be seriously slow down and possibly make its use very uncomfortable. Don't forget.

Functions

In the samples of using functions I assume the VB. language, supposed that the control has been attached to the project through the Components option of the Project menu, and named it AxIThread1.

General functions

SetHandleWnd

Use this function to indicate to the control the handle of the window. The control will send notification messages to this window. Call this function first. (see note).

C Format void SetHandleWnd(long hwnd)
VB. Format SetHandleWnd (hwnd As Long)

- hwnd will be the handle of the window which will receive the messages.

This function does not return any value.

p.e: AxIThread1.SetHandleWnd Me.hWnd

GetLastErrorString

Almost all functions return a boolean value (True or False) indicating the result of its operation. If a function returns ‘False’ it cannot complete its execution (it was called with a parameter out of range, for instance). Calling this function you can obtain a string with the last error produced.

C Format BSTR GetLastErrorString()
VB. Format GetLastErrorString() As String

This function does not take parameters.

It will return a string containing the description of the last error detected. Once the error string is returned, it will clear the error.


p.e: Dim sError as String
If NOT (AxIThread.ThreadPriority (2, 4)) Then
we've got an error
sError = AxIThread1.GetLastErrorString()
MsgBox sError
End If
AppPriorityBoost

Use this function to boost your application's priority or bring it down to normal priority (see Application priority).

C Format void AppPriorityBoost(BOOL bBoost)
VB. Format AppPriorityBoost(bBoost as Boolean)

- bBoost raise priority (True) or back to normal (False).

This function does not return any value.

Thread control functions

ThreadExec

When a thread is created you can indicate if it will be automatically removed when it ends its execution or if it will remain available (see ThreadInit). In this last case, the thread doesn't run inmediately. It waits for an event. When this event is set, then the thread executes a complete cycle and waits again.

Use this function to set the event.

C Format BOOL ThreadExec(short nThread)
VB. Format ThreadExec(nThread As Integer) As Boolean

- nThread event to launch (1 to 8).

If there is an error it will return ‘False’, otherwise ‘True’.

Constants vs. parameters


ThreadInit

It creates one thread with a determined level of priority. Threads could be created suspended or be executed immediately and be removed automatically or not. You can also indicate whether to execute the thread synchronized or not (see thread synchronization).

C Format BOOL ThreadInit(short nThread, short nPriority, BOOL bRun, BOOL bKeep, BOOL bSyncMutex, BOOL bRecursive)
VB. Format ThreadInit(nThread As Integer, nPriority As Integer, bRun As Boolean, bKeep as boolean, bSyncMutex As Boolean, bRecursive As Boolean) As Boolean

- nThread thread to create (1 to 8).

- nPriority default priority level (from the lowest [0] to the highest [4]).

- bRun create suspended (False), or start its execution immediately  (True). 

- bKeep remove the thread when it terminates (False), or keep it until explicit removed (True). Threads behave different depending on this value: when False, the thread executes inmediately the code in your program, when True, the thread waits for an event to do a complete cycle.

- bSyncMutex lock the synchronization section (True), or execute asynchronous (False).

- bRecursive wait until the synchronization section is released (True), or wait a single cycle (False). Each cycle waits a max time of 5 seconds. This parameter has no effect if the thread is asynchronous.

If there is an error it will return ‘False’, otherwise ‘True’.

If you seldom need to execute the code of a thread, create it with bKeep set as 'False'; the thread will execute that code and will be removed. If you execute such code often, set it to 'True' and launch its event (see ThreadExec) each time you need to execute that code. The thread won't be removed upon program termination, or explicit removed with ThreadStop


p.e: create thread #1 suspended with normal priority. It will be automatically removed. Synchronized and recursive.
If (AxIThread1.ThreadInit (1, 2, False, False, True, True)) Then
resume it
AxIThread1.ThreadStat(1, TRUE)
perform a single cycle
AxIThread1.ThreadExec(1)

Constants vs. parameters


ThreadKill

It removes the thread indicated.

Use function ThreadStop when you need to stop the execution of a thread or to remove it. Only if (p.e, your code does not detect the stop flag) ThreadStop cannot remove a thread, use this function to remove it.

C Format void ThreadKill(short nThread)
VB. Format ThreadKill(nThread As Integer)

- nThread Thread (1 to 8) to remove.

This function does not return any value.

ThreadPriority

Use this function to change the priority of one thread.

C Format BOOL ThreadPriority(short nThread, short nPriority)
VB. Format ThreadPriority(nThread As Integer, nPriority As Integer) As Boolean

- nThread thread (1 to 8).

- nPriority level of priority (0-4). ‘2’ will be normal priority. ‘0’, 2 points below. ‘1’, 1 point below. ‘3’, 1 point above normal and '4', 2 points above.

If there is an error it will return 'False', otherwise ‘True’.

Constants vs. parameters


ThreadStat

A thread can be in one of two states: suspended or executing. Use this function to change/check its status.

C Format BOOL ThreadStat(short nThread, BOOL bChange)
VB. Format ThreadStat(nThread As Integer, bChange As Boolean) As Boolean

- nThread retrieve info about thread (1 to 8).

- bChange check thread state (False), or change it (True).

Be careful with the value returned. If you have called this function with the second parameter configured as ‘False’, it will return ‘True’ if the thread is running and ‘False’ if suspended. If this parameter is configured as ‘True’, it will return ‘True’ if the action of resume or suspend the thread succeeded, ‘False’ if failed (or wrong parameter).

Example | Constants vs. parameters


ThreadStop

It stops the external execution of one thread (or all) or removes it (them).

C Format void ThreadStop(short nThread, BOOL bRemove)
VB. Format ThreadStop(nThread As Integer, bRemove As Boolean)

- nThread thread/s to stop or remove (1 to 8; 0 all). 

- bRemove only stop external execution (False) or remove thread too (True).

This function does not return any value.

When you stop the execution of a thread, if you have not configured the code in the event of your program to detect the stop flag, the thread won't be removed until its process ends, though this function may have returned before.

Constants vs. parameters


Tips and tricks

Compile your program in native mode, not only for better performance, but also to avoid problems of violation access with the VB. IDE libraries.

All functions need the handle of the window to which send messages. The function that indicates the control which window use is SetHandleWnd. Call it first. I suggest calling it in the form loading event.

Don't use the threads provided by AxIThread to access directly to DAO objects (databases, recordsets, queries, etc). When, for example, you execute a query, is the database engine which accesses to the database; not your application. The database engine creates the requested object and is the only with access to the portion of memory where the result of the query was generated. These objects could only be handled through the methods provided by the database engine. An attempt to gain access to the memory allocated for such object (from outside the engine) will cause a protection page fault (and your program will be closed). This doesn't mean that you cannot use the data from a thread running in the background, but you must always handle the data within an object generated by your program (a collection, for instance). And, you don't need threads for executing queries to databases; the database engine creates its own threads to process the queries requested.

When you run a thread, it only launches the corresponding event, ThreadFunc (nThread as integer) . You can launch them calling directly from within your program, but the code will run in own process of your application, not in an independent process.


Thread synchronization

The secondary threads can be executed asynchronously or synchronize its execution to the termination of other/s. For instance, a thread which operates with data calculated or retrieved by another thread must wait until the data are available.

AxIThread facilitates one synchronization section. This synchronization section is mutual exclusive so, while a thread works within it, the section is locked and not available for the others until the thread releases it. Any thread can lock this section (see ThreadInit) so threads that need to be synchronized must always wait until the synchronization section is free.

For instance, imagine a thread that renders data while another is responsible of calculate or retrieve such data. If the threads aren't synchronized, you may launch the 'rendering' thread while the 'calculating' is still retrieving data. The resut will be an incomplete rendering. But, if they execute synchronously, the 'rendering' thread couldn't render the data until the 'calculating' one terminates (or vice versa), for they use the same synchronization section which is only available for one of them at a given moment.

Change the application's default priority

In the introduction I comment that if you raise the priority of the secondary threads above the application's primary thread, your program's interface (if secondary threads need much processing time) may be slow down (and its use may be very unconfortable).

To avoid this situation you can use the AppPriorityBoost function to raise your application's priority until the level 10, or bring it down to normal priority.

Although it may look as a good way to raise the performance of your application (and perhaps it may be), the system will have to deal with at least two threads with higher priority than the Windooze user interface (with a dozen of secondary threads). Your application is constantly requesting services from such interface that won't be provided at the usual speed. Use it only when necessary.

Detecting the stop flag

The inside process AxIThread does to execute external code is the following: when you execute code through the external event ThreadFunc (...), the control sets a flag indicating that the thread is busy running code. When the event ends, the flag is cleared. AxIThread knows the status of its threads checking these flags. But the mere fact of knowing there is external code running somewhere, doesn't give it control over that code. If such code gets stuck by any reason or it's necessary to stop it, AxIThread can force it to terminate, but cannot release the memory that such process may have assigned.

To avoid this situation the code in the event must check somehow if it must return. This may be checking the value of an internal variable of your program (boolean, for example), or you can use ThreadStop to send a stop message. Or better, use both.

If you decide to use ThreadStop to notify to the event that it must return, you need to prepare your code to read the stop flag that is sent by this function. ThreadStop will write this stop flag where your application can read it.

Each window has an associated 32-bit value intended for your use. This value is the user data zone. Here, is where AxIThread will write the stop flag. 

The stop flag is one bit with the value of 1. Each thread must read a different bit. Thread #1 must read the first bit, thread #2 the second, etc. If you call ThreadStop with the first parameter configured as 0, AxIThread will write all the flags (the eight bits). So, we are only interested in the eight less significant bits of the 32. TheadStop(1) will set these bits as 0000.0001, TheadStop(2) as 0000.0010... ...TheadStop(7) as 0100.0000, TheadStop(8) as 1000.0000 and TheadStop(0) as 1111.1111.

Your event must read the window's user data to detect the stop flag. The API GetWindowLong function retrieves information of the specified window, like style of the window, address of window procedure... and also the 32 bit value associated to it. Its format is the following:

C Format long GetWindowLong(HWND hwnd, int nIndex)
VB. Format GetWindowLong (hwnd As Long, nIndex As Long) As Long

Where hwnd is the handle of the window and nIndex specifies the zero based offset to the value to be retrieved. In this case, the user data, recognized as the GWL_USERDATA constant or the value (-21).

The function returns the requested 32-bit value or 0 if the function fails.

In your program (if VB.) you must declare it as:

Private Declare Function GetWindowLong _
Lib "user32" Alias "GetWindowLongA" ( _
ByVal hwnd As Long, ByVal nIndex As Long) As Long

copying its declaration from the API text viewer or from an example. You can also declare the constant GWL_USERDATA and a constant for every stop flag, so you need not to remember their values.

Private Const GWL_USERDATA = (-21)
Private Const nStopThread1 As Long = &H1
Private Const nStopThread2 As Long = &H2
Private Const nStopThread3 As Long = &H4
Private Const nStopThread4 As Long = &H8
Private Const nStopThread5 As Long = &H10
Private Const nStopThread6 As Long = &H20
Private Const nStopThread7 As Long = &H40
Private Const nStopThread8 As Long = &H80

Now we are prepared to detect the stop flag; for example:

Private Sub AxIThread1_ThreadFunc(nThread as integer)
Dim nStopFlag As Long

Select Case nThread
Case 1 'code to execute by thread #1
Do While True
nStopFlag = GetWindowLong(Me.hwnd, GWL_USERDATA)
If (nStopFlag And nStopThread1) Then Exit Do
...
...
‘statements to run
...
...
Loop
Case 2 'code to execute by thread #2
...
Case n 'code to execute by thread #n
...
End Select
End Sub

First of all we read, through nStopFlag, the user data of the current window: nStopFlag = GetWindowLong(Me.hwnd, GWL_USERDATA)

then we check the stop bit for the first thread; if set, we exit out of the loop, otherwise execution continues: If (nStopFlag And nStopThread1) Then Exit Do

When we launch this event, the code enters in an infinite loop. We can only exit the loop when the stop flag is set. You must call ThreadStop (1) at some point in the program to set the flag, or call ThreadStop(0), to set all flags. You must compare it bit to bit with And; not (nStopFlag = nStopThread1) for that compares value = value, which will work with ThreadStop(1), but won't work with ThreadStop(0):

nStopFlag AND nStopThread1 (values are binary)

TreadStop (1)-> (0000.0001 AND 0000.0001 -> True)
TreadStop (0)-> (0000.0001 AND 1111.1111 -> True)

nStopFlag = nStopThread1 (values are binary)

TreadStop (1)-> (0000.0001 = 0000.0001 -> True)
TreadStop (0)-> (0000.0001 = 1111.1111 -> False) (and it won't return)

ThreadStop | ThreadKill


Constants vs. parameters

Nearly all functions receive a long Integer as a parameter indicating the operation requested. Thread control functions need as the first parameter a long Integer identifying the thread to which apply its operation.

ThreadPriority (2, 4) set thread #2 priority level to 4.

You can work with long Integers if you want, but you will find it more accurate to define constants and work with them, for instance:

Private Const nThread1 As Long = &H1
Private Const nThread2 As Long = &H2
...
Private Const nHighestPriority As Integer = &H4
Private Const nHighPriority As Integer = &H3
...

Now you can call the function as

ThreadPriority(nThread2, nHighestPriority)

If you prefer using constants in function calls, define the following naming them as you want:

Calling thread controlling functions

Private Const nThread_1 As Integer = &H1
Private Const nThread_2 As Integer = &H2
...
Private Const nThread_7 As Integer = &H7
Private Const nThread_8 As Integer = &H8

Private Const bInitRun As Boolean = TRUE
Private Const bChange As Boolean = TRUE
Private Const bKeep As Boolean = TRUE
Private Const bSuspend As Boolean = FALSE
Private Const bResume As Boolean = TRUE

Private Const nStop_1 As Long = &H1
Private Const nStop_2 As Long = &H2
...
Private Const nStop_7 As Long = &H40
Private Const nStop_8 As Long = &H80
Private Const nStopAll As Long = &HFF

Private Const nLowestPriority As Integer = &H0
Private Const nLowPriority As Integer = &H1
Private Const nNormalPriority As Integer = &H2
Private Const nHighPriority As Integer = &H3
Private Const nHighestPriority As Integer = &H4

ThreadExec | ThreadInit | ThreadKill | ThreadPriority | ThreadStat | ThreadStop

Known problems

Don't forget your flag, please.

AxIThread cancels all its active processes when it unloads, even when the application that started them 'had forgotten' all about them. If you close your application without stopping running code on secondary threads, AxIThread will stop that process for you.

First it will tell the process to terminate setting the stop flag and will wait a short time for an answer. If such answer is not produced, it will raise the thread's priority level and will wait again for the thread to terminate. When not detected, its termination will be forced.

Although AxIThread can always stop the threads, close your pending processes before unloading your program. AxIThread can stop these processes, but he cannot free the memory they may have assigned.

The lazy code

If you are still reading, and decide to put yourself to practice (nothing better than to experiment with the samples), I must warn you about the peculiar (al least) behavior of some aspects of VB. 

We are running code in the background... at a first glance, perhaps you won't notice anything strange, but you will detect that if you have written something like MsgBox "Hi, from the background" the message window is exactly as you have imagined (within the VB. environment). But if you are executing compiled code, you won't see anything. However, if you launch that event calling it directly from within your program, the window appears. The reason may be as simple as that the 'MsgBox' function is not an API function, but an VB. implementation of it, and out of the VB. environment, acts as we expect of it in front a VB. calling, but not in front an extern calling.

Although I have a few ideas about such 'curious' behavior, I will keep them for myself, for the simple reason that my knowledge of the 'intimities' of VB. is not so deep and what I may consider as a deficient implementation, perhaps has a reason of being.

Therefore, I suggest you should check your code executing it within VB. as interpreted code, and outside as compiled code. May not be MsgBox the only lazy function (and I'm too lazy to test all of them). 

In any case, if you see rare behavior in some other function, and you need to use it necessarily, you can always call the API directly. Though it requires a little more work, you will find unsuspected functionalities. 

Unknown problems

This section wouldn't be necessary if we (the programmers) were perfect but as that is not the case we must consider that something may not work correctly.

If you detect some problem working with AxIThread, please send me your comments about the anomaly (describing it as well as possible), to kikusi@arrakis.es indicating something as "Bug detected" or similar.

Before letting me know about an error, check if it is also produced with your program compiled in native mode. It's possible that if it is running within the VB. IDE or compiled as interpreted code (p-code), it may have problems with some VB. library.

Ordering info

I'm not going to tell you what shareware is, if you want you can read the article about shareware at the end of this file. But I'll tell you some considerations about my point of view.

First of all, when designing this control, I was thinking about making use of certain interesting characteristics (only accessible calling the Windooze API) of the 'modern' systems easy. I think you will find it useful and easy to handle, though the utility I assume it has, other people may find it not very useful or simply not adequate to its purpose.

It took me some time and some work until I got satisfied with the performance and accuracy of a component I hope will help you in your developments. 

Secondly, programming is not my only occupation (really, I'm a carpenter), so I must get some kind of compensation for the work I take, and the hours of 'suffering' that my family have to bear with.

I don't like sharing components with unavailable characteristics or time limitation, so AxIThread is totally functional. But I think that if someone gets registered, he must obtain some benefit, despite the little it may be. In this case, the only advantage for registered users (apart from contribute supporting the shareware philosophy) is the removal of the copyright window.

To order, mail me to kikusi@arrakis.es or the mailing address below, indicating the following data:

Name wanted in the license registration (commercial or particular).
Mail address (eMail).
Number of licenses (when not indicated, I will assume 1).
Don't forget to indicate the control to register (AxIThread).

Payments must be in EEC (CEE) euros or US dollars, made by international postal money order or by check payable to:

Miguel Perancho Hevia
San Bartolomeo da Freixa
32514 - Boborás, Ourense
Spain

or (preferred) by bank draft to the account nº:

2038-4028-57-6000036475

indicating in any case the name used to register. Once the payment is effective, you will receive the license key to register the component.

Pricing list:

AxIThread, particular developers -> 15,00 euros, US $ 15.75
AxIThread, educational institutions -> Contact me for special pricing
AxIThread, corporations -> Contact me

For more information or to download click here.

También en castellano.

About shareware

Not long ago, we had to buy commercial programs totally 'blind', evaluating the product after purchasing it. Today, as a previous step of the final product being released, it's very common that we have the chance to test a pre-release, 'Demo' or 'Beta', limited its use to not all of its characteristics or with time limitation, for evaluation purposes. The shareware is quite responsible of this fact.

The time when shareware or freeware programs were worse software solutions has gone, as an example, evaluate any shareware design program that you can probably install from that cd-rom that comes along with your favorite magazine.

As you will know, the philosophy on which shareware is based is 'try before buy'. So, a user can test the excellence (or its absence) of the software before taking the decision of purchasing it. And, normally, there is less money required. It's true (but not always) that a single programmer, or little group of them, cannot compete against a team formed by many persons and millionaire resources, developing complex projects. But, in the other hand, he may offer us other solutions (components, plug ins or programs) these teams don't care about.

Us, those who stay hours and hours sitting in front of (the evil) the computer, occasionally realize that a marvelous program, which occupies over 200 'tasty' megs of our disk and does lots of 'well done' things, lacks a little detail which will make our work simplier or more efficient, and someone (somewhere in the world) has found a way to solve this detail, offering his solution to us, changing its dedication to it somehow.

Shareware is based in the confidence a developer of an utility which saves some of our time has in we will compensate his efforts. It's not unusual to find those who only ask for a letter making them know who we are, where we are and what we think about their work, though the most usual, and in this case I have decided so, is an economic compensation.

This is shareware. I offer something you can take and try on your own. If you, after evaluating, decide to use its functionality, you must accept the conditions under which I offer it.

All this means nothing without the support of the users. If nobody gets registered, the programmer will give up developing, and we will be left in the hands of a few. The advantages (for you) supporting shareware are obvious: you will have thousands of software solutions against (generally more expensive, though generally also more complete) 'commercial' software.

But, al least you will have the chance to select from many possibilities, not from only a few.

Now, it's your decision...



The author (or the one to blame)

For more information or to download click here.