|
AxIThread v3.00, Copyright © El Zooilógico, 2.000
Description
AxIThread can manage up to 25 independent processes running in the background while your application performs another task. Each process will be executed in an independent thread (under AxIThread's control) being your program the responsible of administering its functionality.
For that purpose, the library exposes seven methods to control all the possible actions (creation, removal, priority change, etc.) and two events where to write the code to execute and retrieve errors.
AxIThread can execute any section of code from your program in an independent thread; you only need to set the function that will be the thread's entry point. A thread may have two different entry points, and they could be changed at any moment.
You may also set recursiveness, and the default function will be constantly called; set a timer, and the method will be called when the interval elapses; enable protection, and new notifications will remain locked while a call is being processed, or disable it. And you may always assign a new task to the thread that will be executed with higher preference.
You may 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.
Brief notes about relevant facts
Using AxIThread
To use library's functionality, you must add AxIThread's type library to your project, either through the Components option, or the References option, both in the Project menu (in the VB IDE). In both cases, choose the reference 'AxIThread 3.0 type library'.
In the first case, you will see a new icon on your components palette. To obtain a valid instance you must add it to any form of your project. In the other, you must instance the 'Thread' object of the library,
Public myThreadObject as new Thread (or) Public WithEvents myThreadObject as Thread ... ... Set myThreadObject = new Thread
NOTE: The second declaration enables event notifications.
Recursive/non recursive threads 
A non recursive thread is a passive thread, it remains inactive waiting for a message to process. When it receives a message, it performs a complete cycle to execute the requested task and enters a wait state again until a new message is available.
On the other hand, a recursive thread is an active thread; it processes messages exactly as a non recursive thread does, but when no message is available, it won’t remain waiting, it makes a new call to the default method.
Imagine a function checking if there are data available in a communications port. This function waits a given interval. Once the interval reaches its timeout value (or data become available), this function notifies the fact to its parent process.
This is the function suitable to be called from a recursive thread. This thread invokes the method and notifies results to its parent process. Thus, forever, al least while not receiving a message telling it to perform another action. The parent process is then free from performing a scheduled port checking, and need to attend only the notifications received from the secondary thread.
Recursive threads, will always process first messages posted to its message queue; only when there are no messages posted they will keep invoking the default method. So, requests made from the parent process will always be attended.
Non recursive threads, perform a very efficient wait state, consuming less processor time. Thus, they are suitable to wait for a given event to occur, or perform non scheduled tasks, as it would be read the data (which presence we know thanks to the recursive thread) received at the communications port of the previous sample.
Timers (scheduling)
You may set a time-out interval when creating a thread, thus, a timer will be enabled for it, so it will regularly (depending on the interval set) request the thread to perform a call to a method (which need not to to be the default one).
Such scheduled behaviour is totally independent from the starting mode of the thread (recursiveness state), being also possible, to set different tasks in one case or another; the thread, depending on the message received, will perform one task or another.
Timers may be enabled at thread creation or later; you may disable them, change their time-out interval or the task to perform at any moment.
I must point out that, recursive threads, will always perform either direct or scheduled messages with higher preference.
Running code in the background: the entry point
In an executable file, there aren't descriptions of functions or variables. All references to any of them has been turned into a relative memory address (offset) from the program's entry point. When a program loads into memory, it loads at a very concrete point of it, and any reference to data points to the memory address where it is stored. Such position is the offset from the program's origin address.
Every time a thread is created, it's told to start at a given memory location, that is, its entry point.
AxIThread, exposes an event (ThreadFunc) where you may write the code to execute in an independent thread. In the VB IDE, for instance, you will see it as
Private Function Thread1_ThreadFunc(ByVal nThreadID As Long) As Long (where nThreadID will be the ID of the thread making the call)
This will be the thread's entry point by default, but you may set another entry point when creating the thread. The new entry point function must conform the event's prototype, that is, it must accept a long integer by value, and it must return another long integer, p.e,
Private Function myThreadFunc(ByVal nThreadID As Long) As Long Dim ValueToReturn as Long ... ... myThreadFunc = ValueToReturn End Function
To set the new thread's entry point, you only need to tell CreateThread, the new memory address
In VB, you may use the AddressOf operator; it returns the memory address of the requested data. This operator has the following restrictions,
1- it can only be used in a module (.bas file) 2- it cannot be used directly, it must be used as an argument when invoking a function, so you will need a packing functión to cast the address to a long integer. 3- the AddressOf argument must be in the same module.
For instance,
Private Function PackAddress(FuncAdd As Long) As Long PackAddress = FuncAdd End Function
Public Function GetEntryPoint(whichOne as integer) as long Select case whichOne Case 1 GetEntryPoint = PackAddress AddressOf(ThreadFuncA) Case 2 GetEntryPoint = PackAddress AddressOf(ThreadFuncB) ... ... Case else GetEntryPoint = 0 End select End Sub
Public Function ThreadFuncA(ByVal nThreadID As Long) As Long
'code to execute
ThreadFuncA = 0 End Function
Public Function ThreadFuncB(ByVal nThreadID As Long) As Long
'code to execute
ThreadFuncB = 0 End Function
The code above must be in the same 'bas' module where AddressOf is used, but this doesn't imply that you can't call functions in another section of your program
Public Function ThreadFuncX(ByVal nThreadID As Long) As Long Dim longVar as Long longVar = FormW.FunctionV(ByVal nThreadID) ThreadFuncX = ModuleY.FunctionZ(ByVal nThreadId, longVar, ...) End Function
If you have enabled a timer you may set another entry point to it, so the thread would perform a different task when it is receiving a request from the timer.
Despite of the above, you can change the entry points at any moment. You may also, post a message to request another task (another function), that will be inmediately (and only once) executed, returning then to the default behaviour. Thus, a thread can have three different tasks to perform at a given moment.
All this, allows a great flexibility when running code in the backgroung
So, myThreadObject.CreateThread (No entry point set, default will be used, yourObject_ThreadFunc(...) event)
myThreadObject.CreateThread(1,,-1, GetEntryPoint(2)) myThreadObject.CreateThread(1,,-1, AddressOf(ThreadFuncB)) (only in the same module) (the thread's entry point will be ThreadFuncB)
As you would imagine, the value passed as an entry function, may point to a wrong address. In this case, if the thread is recursive and the wrong address is the default entry point, its state will be changed to non-recursive; if it is an scheduled one, the timer will be disabled. In both cases, you will receive an error code in the ThreadDone event. You will also receive an error code if the wrong address was passed as an argument to a direct message.
Possible values are: -. 2147483632 (hex 0x7FFFFFF0), non-recursive (default function) -. 2147483634 (hex 0x7FFFFFF2), non-recursive (message parameter) -. 2147483635 (hex 0x7FFFFFF3), scheduled non-recursive -. 2147483636 (hex 0x7FFFFFF4), recursive (default function) -. 2147483638 (hex 0x7FFFFFF6), recursive (message parameter) -. 2147483639 (hex 0x7FFFFFF7), scheduled recursive
In 3 & 6, timer has been disabled. In 4 & 5, thread state has been changed to non-recursive.
This event, (you will see it as)
Private Function Thread1_ThreadDone(ByVal nThreadID As Long, ByVal retval as long) (nThreadID, thread making the call; retval, returned value or error code)
not only will tell us the presence of an error, but also will notify us the return value of a thread being removed, so any necessary terminating task may be then performed.
Protected mode
The execution of external code may be (or not) protected: only one invocation to a function will be made at a time; any new notification will remain locked while another function call is being processed. If multiple threads may access from within the body of a function to non 'thread-safe' data (a global variable, for instance) you'd better enable protection to avoid data corruption (see apendix A). If protection is disabled, notifications will be made, regardless how many calls are being processed. If data are 'non-sensible' (thread-safe) such as local variables, returned values, indexed arrays, or even a read-only global, you can disable protection. In one hand, you obtain more security, in the other, enhanced performance. Chossing one model or the other depends on how you implement the code in your program. If each thread has a different entry point, or it doesn't work with non thread-safe data, protection won't be necessary, and the execution of your code will be more efficient
Protection is a per-thread property; thus, if multiple threads work in the same section of code, and such space address is sensible to concurrent access, you must enable protection to each one of these threads. Mutual exclusion only works within protected threads, and it's only guaranteed between them; protected threads don't know nothing about non-protected ones, and these nothing know about those.
Imagine three threads accessing the same function body; this function increments a counter, then checks its value and, depending on it, certain operation is performed or not. If you have enabled protection only in two of them, these two can't access the guarded section simoultaneously, but the third one would enter this function regardless any of the protected ones being executing it or not, and one of the protected threads would enter the function regardless the non-protected is executing it or not, (see apendix A).
Passing arguments
Methods exposed by AxIThread require compulsory and/or optional parameters. You need not to set an optional parameter, thus, the following calls are correct,
nThrID = myThreadOb.CreateThread nThrID = myThreadOb.CreateThread(1,,500,,1,,,nResult) nThrID = myThreadOb.CreateThread(, 1, , , ,25000)
Optional parameters need not to be set, but the arguments you set must be in their position.
Methods
In the format description of methods, arguments in brackets are optional. Also, if a value is indicated, it will be the default value for the argument, when not given.
PauseThread
Suspends thread execution until the thread is resumed again or removed
This method doesn't return any value.
Formats (C & VB)
void PauseThread (long nThreadID)
PauseThread (nThreadID as long)
nThreadID, ID of the thread to suspend.
ResumeThread
Resumes a thread previously suspended.
This method doesn't return any value.
Formats (C & VB)
void ResumeThread (long nThreadID)
ResumeThread (nThreadID as long)
nThreadID, ID of the thread to resume.
ThreadPriority
Sets thread's priority to the value indicated in nLevel.
This method doesn't return any value.
Formats (C & VB)
void ThreadPriority (long nThreadID, [long nLevel = 0])
ThreadPriority (nThreadID as long, [nLevel as long = 0])
nThreadID, thread's ID.
nLevel, new priority level. Posible values are from 0 (lowest) to 4 (highest), being 2 the system's default value (8).
CreateThread
Creates a new thread of execution, according the requested parameters; the new thread will always execute a complete cycle when it starts its execution.
If there is no error it will return a long integer containing the thread's ID. You must use this ID when calling other methods. Otherwise, it will return 0, and will set an error in nResult.
Formats (C & VB) long CreateThread([BOOL bProtected = true], [short nPriority = 2],[long nRetCode = 0], [long FuncAdd = 0], [BOOL bRecursive = false], [long nAutoCallInterval = 0], [long AutoFuncAdd = 0], [long *nResult]
CreateThread([bProtected As Boolean = true], [nPriority As Integer= 2], [nRetCode As long = 0], [FuncAdd As long = 0], [bRecursive As Boolean= false], [nAutoCallInterval As long = 0], [AutoFuncAdd As long = 0], [nResult As long]
bProtected, protection enabled (true), or disabled (false). By default, it will be enabled, (see note).
nPriority, default priority level. Allowed values are from de 0 (lowest) to 4 (highest). Default is normal (2).
nRetCode, thread's return code (value to force thread's exit), (see footer).
FuncAdd, thread's entry point memory address, (see note).
bRecursive, when (true) thread will be started as recursive, (see note).
AutoCallInterval, scheduler's time-out interval, (see note).
AutoFuncAdd, scheduler's entry point memory address.
nResult, creation result.
Returned error codes 0, no error. 1, thread was created, but scheduler wasn't (couldn't enable timer). 15, (hex. F), thread wasn't created (either there are 25 threads running, or wasn't possible).
A thread remains active until the library unloads, it is told to exit by ThreadStop, or it receives the return code that was set to it. If you set a return code to the thread, and any function called within it, returns that value, the thread will exit and it will be removed. This value is null by default (0). If any function doesn't return nothing (and nothing is the same as 0), the thread will get its return code and it will exit inmediately. Take into account that a thread may be required to perform up to three different tasks at a given moment, but it has only one return code, so any of these functions can force it to exit.
StopThread
Stop the execution of thread nThreadID.
If there is no error, it will return 0; or 15 (hex 0xF) if thread nThreadID doesn't exist.
Formats (C & VB) long StopThread(long nThreadID, [BOOL bIgnoreMsgQueue = false])
StopThread(nThreadID As long , [bIgnoreMsgQueue As Boolean = false]) As long
nThreadID, ID of the thread to stop.
bIgnoreMsgQueue, ignore pending messages. When false, pending messages will be processed before removing the thread. When true, the thread will be removed inmediately, ignoring pending messages.
LoopThread
Post a new message in nThreadID message queue. Optionally, you may also set a memory address where to perform the requested action.
If there is no error, it will return 0; or 15 (hex 0xF) if thread nThreadID doesn't exist.
Formats (C & VB) long LoopThread(long nThreadID, [long FuncAdd = 0])
LoopThread(nThreadID As long, [FuncAdd As long = 0]) As long
nThreadID, thread's ID.
FuncAdd, memory address where to perform a cycle.
Recursive threads are constantly invoking its default method; programmed (scheduled) ones, every time the time-out interval elapses; recursive-scheduled in both cases. However, non-recursive nor programmed threads, call its default entry point once when they are created, and then remain inactive waiting a request to perform a new cycle.
Thus, this function covers two purposes:
first, to request a recursive and/or scheduled thread to perform an action different from the default, call this function, with the memory address of some function of your program. Thus, the thread may be required to perform up to three different tasks at a given moment.
secondly, to request an inactive thread (non-recursive nor programmed) to perform a new cycle either on its default method, or over the function indicated as FuncAdd.
ModifyThread
Toggles recursiveness state; sets, modifies or removes a scheduler; changes the default's and the scheduler's entry point.
If there is no error, it will return 0; 1 if is not posible to set a timer or 15 (hex 0xF) if thread nThreadID doesn't exist.
Formats (C & VB) long ModifyThread(long nThreadID, [BOOL bToggleState = 0], [long FuncAdd = 0],[long nNewInterval = 0], [long AutoFuncAdd = 0])ModifyThread(nThreadID As long, [bToggleState As Boolean= 0],[FuncAdd As long = 0], [nNewInterval As long= 0], [AutoFuncAdd As long = 0])
nThreadID, ID of the thread to modify.
bToggleState, toggle recursiveness state.
FuncAdd, new default entry point, (see note - see footer also).
nNewInterval, new scheduler's interval, (see note).
AutoFuncAdd, new scheduler's entry point, (see footer again).
Values set in FuncAdd and in AutoFuncAdd will be interpreted:
- (-1), no changes. - (0), disable entry point; from now on, calls will be made on the default ThreadFunc event - any other value will be treated as the new entry point.
If the value set as entry point is not correct you will receive an error event indicating the cause and the action taken, (see note).
Appendixes
About protected mode
If you plan to use multiple threads accessing the same section simultaneously and don´t want to enable protection, don't use global variables in the body of the notification function, except in case they are read-only variables. Look at the following piece of code:
Private Function EntryPointFunction(ByVal nThreadID As Long) as longGlobalData = GlobalData + 1 If GlobalData = 5 Then... EntryPointFunction = ThreadReturnValue ...End IfEnd Function
Every time this function is called, the value of the GlobalData variable is incremented; then the resulting incremented value is compared to 5. If both values match, certain operations are performed and the thread's default return code is set. If a thread call this function when GlobalData is set to 3 and increments its value but, before checking the result, the system gives control to another thread that also calls this function and increments the value of the variable, when control returns to the first one the content of GlobalData will be 5 (and it will enter the if block), not the expected 4.
This is the kind of situation you must avoid, either using local variables (so each thread will work with its own copy), or using arrays,
Private Function EntryPointFunction(...)GlobalData(nThreadID) = GlobalData(nThreadID) + 1 If GlobalData(nThreadID) = 5 Then... EntryPointFunction = ThreadReturnValue(nThreadID) ...End IfEnd Function
If you absolutely need to check a global variable, make sure it is read-only or implement some kind of synchronization. As a very simple example,
Private Function EntryPointFunction(...)Static Lock as Boolean
Do while LockSleep(1) 'give control to another threadLoop
Lock = true GlobalData = GlobalData + 1 If GlobalData = 5 Then... EntryPointFunction = ThreadReturnValue ...End If Lock = falseEnd Function
If you still experiment problems, then you must enable protection.
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 library, 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.
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 (corporate licenses only). Don't forget to indicate the library 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 wire transfer 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 library.
Pricing list: (Jan-Dec 2.000)
AxIThread, particular developers,
Single license, 70 euro, $70 US
AxIThread, corporations,
Single site license, 425 euro, $425 US
Full license, contact me
AxIThread, educational institutions,
Contact me for special pricing
Remember, AxIThread is a library, not a program. It's the developer who satisfies the registration fee, not the final user. Once registrated, a developer obtain a PER SITE full license, you may use it in your projects and deploy it without any charge. Obviously, one developer-one site, one license.
For more information - To download.
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/her 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 - To download.
AxIThread v3.00, Copyright © El Zooilógico, 2.000
| |