|
libThr v2.70.058, Copyright © El Zooilógico, 2.000
Description
libThr posibilitates the simultaneous execution of a maximum of 99 concurrent processes. For that purpose, it exposes a COM interface, ThreadInterface, which facilitates the creation of COM/OLE objects within independent executing threads. Any object which supports automation, can be created within a thread through libThr’s exposed functionality.
libThr supports both, synchronous and asynchronous returned values notifications. When invoking a method of the object created within a thread through libThr´s interface, it may wait for a returned value until it is produced, or return inmediately and notify it through an asynchronous event when it becomes available.
Moreover, libThr supports both ‘single-threaded Apartment’ threading models, and ‘free multi-threaded’ threading models; you may execute multiple objects of one of this models (apartment threaded or free threaded) or a mixture of threads regardless of their threading model. Every new object requested, will be initialized according to the threading model that was set to it. This operation is performed internally, and is transparent to the developer.
libThr is an ‘in-proc’ library, that is, its code runs in the same process that has opened an instance of it. Its use is mainly intended to facilitate simultaneous use of multiple OLE objects; either existing objects, or to run you own services and libraries.
You can concentrate yourself in the implementation of your object’s functionality, and forget all about the gory details of synchronization, scheduling... for libThr will do it for you.
Brief notes about relevant facts
Using ThreadInterface
To use ThreadInterface functionality you must add to your project the libThr type library. For instance, within the VB IDE, open the references option in the Project menu and select the ‘libThr 2.7 type library’ reference. Now you may create instances of the ThreadInterface interface.
And declare an objet derived from ThreadInterface
Public myThreadObject as new ThreadInterface (or) Public WithEvents myThreadObject as ThreadInterface ... ... Set myThreadObject = new ThreadInterface
NOTE: The second declaration enables asynchronous notification calls.
Thread creation process
Once you have created an instance of ThreadInterface, you can then call the CreateThread method every time you need a new thread. Threads are created according the following model:
a. first of all, the thread is created. b. from within the new thread, a new instance of the requested object is created. c. the thread, depending on creation arguments, either waits for a message to process (non recursive thread), or makes an scheduled call to the default method provided (recursive thread), checking if there is a message available before every new call loop. In both cases, thread remains active until it’s told to exit. d. the reference of the object is released. e. finally, the thread is removed.
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 an asynchronous 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.
All threads have a message queue, so any message that couldn’t be processed when it’s posted (because an external method is being invoked, for instance), will be attended in the same sequence it was notified. You can configure the maximum number of pending messages to process between the minimum allowed (4) and the maximum permitted (32). Any message posted to a thread that has reached its maximum number of pending messages posted, will be rejected until the number of messages comes below the maximum permitted to it. In this case, a well-known error code will be returned. By default, this maximum is set to 15.
Synchronous & asynchronous notifications
When you invoke a method of the object which lives in a secondary thread, if a return value is expected, either we may wait until the method returns (synchronous model), or we can indicate we want the returned value to be notified to us when it becomes available and return inmediately (asynchronous model).
Each model has its advantages. The asynchronous model, frees the parent process from wait states, so it can perform another task. The synchronous model, waiting for a result, can’t perform another action, but it will be adequate when we need the result to continue (to perform a calculation, or take a decission, p.e).
In both cases, the execution of the external (the object´s) method is performed in the secondary thread, but, I must insist here, the processing of the returned value is performed in the main process (in the synchronous model), while such processing is also performed in the secondary thread (in the asynchronous model). Thus, if the response to the returned value is, for instance, to create a modal window where to show the result, in the synchronous model, while the window is opened, the main process remains ‘frozen’, but the asynchronous model will show the window, without freezing the main process.
If you keep together synchronous and asynchronous threads, only one of the synchronous may show the window, while the others keep waiting for the main process to attend them, while asynchronous ones can show a window each one, regardless the state of the main process, even simultaneously to a synchronous window being shown. Even more, perhaps all this taking place at the same section of code of your program.
Choosing one model or another depends on the task a thread is supposed to do, and it cannot be modified once defined. But, at a given moment, you can force the inverse behaviour setting maximum wait intervals (synchronous to asynchronous), or forcing a return value call (asynchronous to synchronous).
NOTE: All this, doesn´t mean nothing if you have not enabled asynchronous notifications. This can be made (p.e. in the VB IDE) declaring the key WithEvents when instancing the ThreadInterface class.
Dim WithEvents myThreadObject as ThreadInterface
Now, you may see two events (again, in the VB IDE) named as:
myThreadObject_AsyncRetval(nThreadID as long, retval as variant) myThreadObject_GotCOMError(nThreadID as long, nErrorID as long)
AsyncRetval, here is where asynchronous notifications will be made, receiving the ID of the thread making the call and the returned value (when available).
GotCOMError, will receive OLE/COM invocation errors.
Even more, enabling event notifications implies not only declaring the class as WithEvents, but also setting the corresponding values in the flags argument of the CreateThread method.
I must explain, the protected mode bit of the flags argument. Asynchronous notifications can be made protected or not protected: in the first case, notifications will be made one by one, locking new calls while a notification is being processed. If the notifications must access non thread-safe data that may be corrupted if handled by multiple threads (a global variable, for instance), you'd better enable protection (see note). When protection is disabled, all notifications will be made regardless the number of calls in progress. If such notifications access 'non-sensible' data like local variables, returned values, indexed arrays (or even read-only globals), you may disable protection. On one hand you obtain more security, on the other hand, enhanced performance. Enabling protection depends on how you plan to handle 'thread-sensible' data.
Invocation kinds
When calling CreateThread, StopThread and Exec methods, you may, optionally, indicate the name of one of the object’s function you want to invoke. In this case, you must also indicate the invocation kind such method requires.
Methods exposed through COM interfaces, are invoked according the functionality they implement. The most relevant are:
1, Method (DISPATCH_METHOD), the invoked function is a method. It can return a value or not.
2, Property, (DISPATCH_PROPERTYGET). The invoked function returns the value of a property.
4, Property, (DISPATCH_PROPERTYPUT). The invoked function sets a property to the given value.
8, Property, (DISPATCH_PROPERTYPUTREF). Same as above, but the value is passed by reference, rather than by value.
When you want a function to be called, you must indicate the invocation kind (1,2,4 or 8), so ThreadInterface can make the proper call.
libThr, may look for the required invocation kind, but this will require to load the type library from the file which exposes the object and identify the requested interface, and after, in every new invocation, locate the method requested and load its description, in order to get the invocation call it expects.
The type library is implemented at library level, not at object level. In an environment, where multiple instances of the same object may access simultaneously to the type library of the file where they are implemented, it is not efficient neither have a shared description of it for all threads, nor have a copy for every thread. In the first case, due to synchronization issues, in both cases, due to performance loss too.
This is the reason I have decided not to implement this functionality for.
Passing arguments
Methods exposed by the ThreadInterface class require obligatory parameters and/or optional parameters. Optional are divided into those who take a default value when such value is not given, and those who don’t take a default value. In any case, all optional parameters need not to be set, for it will take a default value or it will be ignored. For instance, the following calls are all valid:
newThreadID = myThreadObject.CreateThread ("myDll.myClass") newThreadID = myThreadObject.CreateThread ("myDll.myClass", , , , , , , , nResult) newThreadID = myThreadObject.CreateThread _
("myDll.myClass", 5000, , , 4,"SetData", "InitialValue")
You need not to set the optional parameters you don’t need, but the parameters you may set must be in their valid position.
Parameters you need to pass to an OLE object´s method, must be set if the params argument. This is a variant type, which may be a single variant or an array of variants. If you need to pass more than a single argument, it will be an array of variants; if it is only a single parameter, you may pass it as an array of a single element or as a single variant indistinctly.
For instance:
myThreadObject.Exec (newThreadID, ,4 , ,"SetData", "Value") Dim varParams(3) varParams(0) = True varParams(1) = "This is a string" varParams(2) = 2000 varParams(3) = "Another string" myThreadObject.Exec (newThreadID, True, 1, ,"Method_1", varParams, varRetVal)
If the method you are going to invoke has optional parameters, and you want them to take their default value, you must leave its position empty, so ThreadInterface make the call with the arguments in their proper position.
Dim varParams(3) varParams(0) = True varParams(2) = 3000 myThreadObject.Exec (newThreadID, True, 1, ,"Method_1", varParams, varRetVal)
NOTE: to avoid creation of one array of variants with a different dimension each time you need to invoke an object’s method, you may create a packing function which accepts a variable number of arguments, and make a call to it, without giving a second thought about array dimensions.
Public Sub Exec( ThrId as long, Function As String, Optional InvKind as Integer, _
ParamArray() params)myThreadObject.Exec (newThreadID, , InvKind, ,Function, params)End Sub
Now, you can make the calls,
Exec (newThreadID, "SetData", 4, "Value") Exec (newThreadID, "Method_1", 1, True, "This is a string", 2000, "Another string") Exec (newThreadID, "Method_1", 1, True, , 3000)
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 for the object indicated in the ThreadReference parameter. Optionally, a function of the object may be invoked first, when the thread starts its execution (to initialize one of the object’s properties, for instance). The thread will be created depending on arguments set in the flags parameter. If there is no error, or it is not a severe error, CreateThread returns a long integer containing the ID asigned to the thread, which identifies it. This ID must be indicated when calling all the other methods exposed by ThreadInterface. Otherwise, it will return 0 and will set an error code in nResult.
Formats (C & VB) long CreateThread(LPCTSTR ThreadReference, [long WaitTimeout = 2500], [long flags = 3843], [LPCTSTR RecursiveFunction], [short invKind = 1], [LPCTSTR Function], [VARIANT *params], [VARIANT *retval], [long *nResult]) CreateThread(ThreadReference as string, [WaitTimeout as long = 2500], [flags as long = 3843], [RecursiveFunction as string], [invKind as integer = 1], [Function as string], [params as variant], [retval as variant], [nResult as long]) as long
ThreadReference, will be a string containing the object’s ProgID. You must set this ProgID to an automation object's ProgID, ‘dll or exe file.class to instantiate’, for instance ‘Excel.Application’ o ‘mydll.myclass’.
WaitTimeout, long integer; the max. interval time (miliseconds) allowed to perform a wait state for an object’s function to return. Default value is 2500. You can set it to any number of miliseconds, 0 (not to wait), or -1 (wait forever).
flags, long integer which will be read as a bit field. It’s divided in three sections: starting mode (protected/non protected, synchronous-asynchronous, recursive-non recursive,...), priority level and max. pending messages number allowed in the message queue, (see below).
RecursiveFunction, string containing the name of an object’s method which will be invoked by default by a recursive thread. If you have not set the recursive flag in the flags argument, it will be ignored. This function, must be a method (invocation kind 1), (see note).
invKind, invocation kind for the method indicated as Function, (see note).
Function, string containing the name of a function to invoke first when thread starts its execution. This will be invoked only once, and always prior of any other operation.
params, parameter/s to pass to the invoked method, (see note).
retval, variant where to put the invoked method result.
nResult, long integer containing the result of the operation.
Returned error codes
0, no error. 1, recursion bit set, but RecursiveFunction is not set. Thread was created non recursive. 2, wait interval timeout reached, (see apendix A). 4, Out of memory. Thread was created, but Function won’t be invoked, (see again). 15, (hex. F), there are 99 threads running, no more threads allowed. 16, (hex.10), thread creation failed (usually an asynchronous return bit was set in the flags field, but the object wasn't instantiated to perform asynchronous processing) (see note). 160, (hex. A0), requested object doesn’t have a valid threading model. 176, (hex. B0), couldn’t initialize COM for this thread. 192, (hex. C0), the requested ProgID is not registered as an automation object. 208, (hex. D0), couldn’t create a new instance of the object. 224, (hex. E0), the object doesn’t implement IDispatch interface. 240, (hex. F0), couldn’t find a function in the object corresponding to the one set in RecursiveFunction.
Error codes above 15 (hex F), are severe error notifications, the thread wasn’t created. Codes below 14 (hex E), are weak error notifications; thead was created successfully, but some requirement was not met. As thread creation may have succeded (if there is not a severe error), these error codes are aggregated to the returned value. Thus, 193 (hex. C1), tells us that the recursive flag was set, but the name of the function to invoke recursively wasn’t; it also tells that the thread wasn’t created for the ProgID is invalid.
NOTE about the flags argument flags, is a long integer, but it will be read as a bit field. It is divided in three sections:
0000.0000.0000.0000.0000.0000.0000.0000 (the 16 more significant bits are ignored)
starting mode (bits 0 to 4)
Bit 0, protect asyncronous calls (1), or do not protect (0). Default, 1. (see note) Bit 1, notify COM errors (1), don’t notify (0). Default, 1. Bit 2, recursive bit. Start thread recursive (1), non recursive(0). Default, 0. In this case, you also need to set the ‘Recursive function’ argument; otherwises the bit will be cleared. Bit 3, asynchronous calls enabled (1), or disabled (synchronous) (0). Default, 0. Bit 4, enable asynchronous calls for recursive function (1), disable(0). Default, 0.
bit 3, enables asynchronous calls after processing a message (recursive and non recursive threads) bit 4, also enables asynchronous calls after a recursive thread default invocation loop. If the thread isn’t recursive, it’s ignored.
priority level (bits 5 to 7)
This will be the desired default priority level for the new thread. Posible values are 0 to 4. Default is 0. Priority may be normal (level 2), one point below (1), the lowest (0), one point above (3) or the highest (4). You can modify the thread’s priority later, see ThreadPriority.
pending messages (bits 8 to 15)
Max. messages allowed to be posted to the thread’s message queue. The lowest value allowed is 4 and the highest 32. Default, 15.
How to implement the flags field.
flags = (message_queue * 256) + (priority * 32) + starting_mode bits For instance, non protected asynchronous and recursive thread, with normal priority and a message queue of 24 messages at a time. It will also notify about COM errors. flags = (24*256) + (2*32) + (16+4+2=22) [= 6262 (hex. 0x1876] As you can see the default value (3843 hex. 0xF03), means a 15 message queue (15*256), lowest priority (0*32), protected asynchronous calls (1) and COM errors notification enabled (2).
StopThread
Stops thread execution. The thread to stop will be the one with the ID set in nThreadID. Optionally, you can indicate a function that will be invoked just before thread removal. This method by default ignore returned values. You can override such behaviour setting bWantReturn as true; thus, the value will be returned, but asynchronously. If asynchronous calls are disabled, the returned value is ignored again.
If there is no error it will return 0, otherwise an error code.
Formats (C & VB) long StopThread(long nThreadID, [BOOL bIgnoreMsgQueue = false], [BOOL bWantReturn = false], [short invKind = 1], [LPCTSTR Function], [VARIANT *params]);
StopThread(nThreadID as long, [bIgnoreMsgQueue as Boolean= false], [bWantReturn as Boolean = false], [invKind as integer = 1], [Function as string], [params as variant]) as long
nThreadID, thread´s ID. The same ID returned by CreateThread.
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.
bWantReturn, force returned value notification, (if there isn’t any value set as Function, it’s ignored).
invKind, invocation kind for the method indicated as Function, (see note).
Function, string containing the name of a function to invoke prior of thread’s exit.
params, parameter/s to pass to the invoked method, (see note).
Returned error codes 0, no error. 1, bWantReturn was set but Function wasn’t. Not any value to return. 4, Out of memory. Function won’t be invoked, (see apendix A). 15 (hex. F), the ID doesn’t match with any of running threads’ ID.
Exec
Post a new message in the thread´s (referred as nThreadID) message queue.
If there is no error it will return 0, otherwise an error code.
Formats (C & VB) long Exec(long nThreadID, [BOOL bWantReturn = false], [short invKind = 1], [VARIANT *lpDispatch], [LPCTSTR Function], [VARIANT *params] [VARIANT *retval]);
Exec(nThreadID as long , [bWantReturn as boolean= false], [invKind as integer = 1], [lpDispatch as variant], [Function as string], [params as variant] [retval as variant]) as long
nThreadID, thread´s ID. The same ID returned by CreateThread.
bWantReturn, force synchronous returned value notification.
invKind, invocation kind for the method indicated as Function, (see note).
lpDispatch, variant containing a valid reference to another object. It will be used to invoke Function instead of the default reference, (see below).
Function, string containing the name of a function to invoke.
params, parameter/s to pass to the invoked method, (see note).
retval, variant where to put the invoked method result.
Returned error codes
0, no error. 1, Function wasn’t set. Returning without doing any operation. 2, wait interval timeout reached, (see apendix A). 4, Out of memory. Function won’t be invoked, (see again). 8, message was rejected, the max. number of pending messages has been reached (see note). 15 (hex. F), the ID doesn’t match with any of running threads’ ID.
NOTE about using lpDispatch
Sometimes, the value returned by a given method X of a given object Y, will be a reference to another object’s interface Z. You may use such reference Z, when calling the Exec method, telling this method that the function it must invoke belongs to the interface pointed by Z and not to the default interface Y. For instance,
Dim myObject as new ThreadInterface Dim Database as variant Dim Recordset as variant Dim myThreadId as long Dim retval as long
Public Sub SomeSub() Set myObject = new ThreadInterface myThreadId = myObject.CreateThread("DAO.DBEngine.xx", -1 _ , , , 1, "OpenDatabase", App.Path & "data\sample.mdb" _ , Database, retval) myObject.Exec (myThreadID, True, 1, Database, "OpenRecordset" _ , "select * from titles", Recordset, retval) End sub
You may even pass such reference (really a pointer to the object) between threads, but there is a basic rule you must conform to: if the object follows the single-threaded apartment model (see apendix C), it must be invoked from only one thread at a given time; if you try to invoke a method of the object from the thread that has created it, and simultaneously, pass a pointer to the object to another thread which tries to invoke a method of the same object at the same time, it can affect the object’s data integrity; trying to set a property value from within two threads simultaneously, for instance.
Each thread keep its own data, the asociated OLE object and the object´s data under a synchronizing layer, so a simultaneous write/read access to them is not posible. But if you pass pointers from one thread to another, you must implement some kind of synchonization to avoid concurrently access to its data.
Remember, the object instantiated when the thread was created is guaranted to be thread-safe. Objects or interfaces to objects (like in the previous example) created by this ‘main’ object, are thread-safe in the same context they were created. But, if you pass this interface pointer to another thread, you are the responsible of implementing some kind of synchronization.
Appendixes
About cryptic error messages
Some error messages require a better explanation.
2, wait interval timeout reached
One of the arguments you
can set when a thread is about to be created, is the maximum wait state timeout
value. That means, the maximum time (miliseconds) to wait for an object's method
to return. If the method of the OLE object reaches such timeout value,
CreateThread or Exec will return without waiting for the object's method
to return. This doesn't mean that the object's method didn't return anything,
nor the method was suspended. This means only that we are not going to wait more
time for it to return. If you have not enabled asynchronous calls, the value
returned by the method when it returns will be lost. You may, either set a large
timeout interval (not a good idea), or enable asynchronous calls. Also, you can
always override the default behaviour of a thread and force
(only in the Exec method) the method to return a value, setting
bWantReturn as true; when this flag is set the method will wait
until the object's method returns.
4, Out of memory
This would be a very exceptional error. As each thread keeps
a message queue of 4 to 32 messages, arguments passed to the message queue are
dynamically allocated and deallocated. This error means that there is no physical
memory available to allocate new data.
Interceptable COM errors
Once you have a brand new object in a running thread and
invoke its methods through Exec, it may be that a method you have invoked
doesn't exist in the object, or the parameters you have set are invalid,
or whatever. There is an event (GotCOMError) which will notify us any COM
exception raised, letting us know what's going wrong. Its use is intended for
debug purposes only; when still developing a library or system service,
where debugging OLE errors is not easy. In a final release, you may better
disable this kind of notification.
This is a listing of possible errors
trapped (at least the most common).
OLE error code | Description |
-2147024882 | Out of memory :-( no comment ;-) |
-2147352570 | One or more of the names were not known |
-2147352562 | The number of arguments provided is different from the number of arguments accepted by the method or property |
-2147352567 | The object needs to raise an exception |
-2147352573 | The requested member does not exist, or the invoked call tried to set the value of a read-only property |
-2147352569 | This implementation does not support named arguments. Usually, a property set invocation kind was provided but the method is not a property |
-2147352566 | One of the arguments could not be coerced to the specified type |
-2147352572 | One of the parameters does not correspond to a parameter on the method |
-2147352571 | One or more of the arguments could not be coerced |
-2147352561 | A required parameter was omitted |
|
Multithreading models
At the beginning of this document I stated that libThr
supports both single-threaded Apartment model, and free multi-threaded model.
Single-threaded apartment
Using single-threaded apartments (apartment model) offers
a message-based paradigm for dealing with multiple objects running concurrently.
It allows you to write more efficient code by allowing a thread that is waiting
for some time-consuming operation to allow another thread to be executed.
OLE creates a hidden window in each single-threaded apartment.
A call to an object is received as a window message to this hidden window. When
the object's apartment retrieves and dispatches the message, the hidden window
will receive it. The window procedure will then call the corresponding interface
method of the object.
Thus, when multiple clients call an object, the calls are
queued in the message queue and the object will receive a call each time its
apartment retrieves and dispatches messages. Because the calls are synchronized
by OLE and the calls are always delivered by the thread that belongs to the
object's apartment, the object's interface implementations need not to provide
synchronization.
A logical grouping of related objects that all execute on the
same thread, and so must have synchronous execution could live on the same
single-threaded apartment thread. An apartment-model object cannot, however,
reside on more than one thread. Calls to objects in other processes must be
made within the context of the owning process, so distributed COM switches
threads for you automatically when you call on a proxy.
Within an apartment, interface pointers can be passed without
marshaling. Thus, all objects in one single-threaded apartment thread communicate
directly. Interface pointers must be marshaled when passed between apartments
(marshaling is the process of packaging and unpackaging parameters so a remote
procedure call can take place).
Multi-threaded apartment
In a multi-threaded apartment, all the threads in the
process that have been initialized as free-threading reside in a single
apartment. Therefore, there is no need to marshal between threads. The threads
need not retrieve and dispatch messages because OLE does not use window messages
in this model.
Calls to methods of objects in the multi-threaded apartment
can be run on any thread in the apartment. There is no serialization of calls -
many calls may occur to the same method or to the same object simultaneously.
Objects created in the multi-threaded apartment must be able to handle calls on
their methods from other threads at any time.
Multi-threaded object concurrency offers the highest
performance and takes the best advantage of multi-processor hardware for
cross-thread, cross-process, and cross-machine calling, since calls to objects
are not serialized in any way. This means, however, that the code for objects
must provide synchronization in their interface implementations.
Multiple clients can call an object that supports
free-threading simultaneously from different threads: In free threaded
out-of-process servers, OLE creates a pool of threads in the server process
and a client call (or multiple client calls) can be delivered by any of these
threads at any time. An out-of-process server must also implement synchronization
in its class factory. Free threaded, in-process objects can receive direct
calls from multiple threads of the client.
The client can do OLE work in multiple threads. All threads
belong to the same multi-threaded apartment. Interface pointers are passed
directly from thread to thread within a multi-threaded apartment so interface
pointers are not marshaled between its threads.
The client thread will suspend when it makes an OLE call to
out-of-apartment objects and will resume when the call returns. Calls between
processes are still handled by RPC.
Tips to get the best performance
You will get the best performance using free-threaded objects, because OLE calls are not serialized, and multiple threads can access the same object at the same time.
If you plan to use multiple threads with asynchonous notifications simultaneously and don´t want to enable protection, don't use global variables in the body of the asynchronous notification function, except in case they are read-only variables. Look at the following piece of code:
Private Sub YourObjet_AsyncRetval(ByVal nThreadID As Long, ByVal retval As Variant)If IsMissing(retval) Or IsEmpty(retval) Or IsNull(retval) Then Exit Sub GlobalData = GlobalData + 1 If GlobalData = 5 Then... GlobalData = 0 ...End IfEnd Sub
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 its value is set to 0. 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 Sub YourObjet_AsyncRetval(...)If IsMissing(retval) Or IsEmpty(retval) Or IsNull(retval) Then Exit Sub GlobalData(nThreadID) = GlobalData(nThreadID) + 1 If GlobalData(nThreadID) = 5 Then... GlobalData(nThreadID) = 0 ...End IfEnd Sub
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 Sub YourObjet_AsyncRetval(...)Static Lock as Boolean If IsMissing(retval) Or IsEmpty(retval) Or IsNull(retval) Then Exit Sub
Do while LockSleep(1) 'give control to another threadLoop
Lock = true GlobalData = GlobalData + 1 If GlobalData = 5 Then... GlobalData = 0 ...End If Lock = falseEnd Sub
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 libThr
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 cmaestra@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 (libThr).
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)
libThr, particular developers,
Single license, 62 euro, $62 US
libThr, corporations,
Single site license, 375 euro, $375 US
Full license, contact me
libThr, educational institutions,
Contact me for special pricing
Remember, libThr 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.
libThr v2.70.058, Copyright © El Zooilógico, 2.000
| |