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.
|