Comparison of the Thread objects in the ActiveX Pack1 (ALP
run-time library)
newObjects ActiveX Pack1 (Active Local Pages run-time library)
contains two different classes that allow the applications to run
code in separate thread asynchronously. They are COMThread
and COMScriptThread. We pose
them this way:
- COMThread (alone or in combination with the COMApartment
on desktop Windows OS-es) allows various multi-threading and
advanced COM techniques to be performed. COMThread often
requires deeper COM knowledge from the developer, but allows
even scripting applications (such as ASP applications for
example) to run tasks asynchronously. We recommend it for
usage with quality COM classes - such as components written in
C++ optimized for freethreaded COM apartments. On desktops it
can be also used (together with COMApartment) with scripts,
Apartment threaded classes (written in VB for example) and so
on. However there is a set back - on Windows CE based devices
COM system does not support singlethreaded apartments and the
required marshaling features. Thus any code that initializes
threads in single-threaded apartments will not be compatible
with these devices!
In short: Use this object to call asynchronously
methods on free-threaded COM objects (both desktop and CE) or
COM objects packed in COMApartments (on desktops only).
- COMScriptThread is very easy to use threading tool
for scripting. In other words with it only scripts (for
example VBScript, JScript) can be executed asynchronously in
separate threads. It does not require deep COM knowledge from
the developer and does not depend on COM support for
single-threaded apartments. This means that it is compatible
with both desktop and embedded versions of Windows (e.g.
Pocket PC, smartphones etc.). To achieve simplicity and
cross-platform compatibility it poses certain limitations -
thinner communication between the thread and the application,
only scripts can be executed. However the COMScriptThread
object allows easy management of the thread and flexibility
enough for quite complex tasks.
In short: Use this object to run scripts (or call
routines implemented in them) asynchronously, if your
application needs to run scripts in separate threads and also
needs compatibility with Windows CE.
Need of thread
At the first thought you may wonder why threads would be needed
in scripting applications - such as ASP pages for example. By
default ASP pages are programming environment based on
request-response. Therefore the usual life cycle of a page is very
limited - no more than a few seconds. This makes it harder to
implement time consuming tasks directly in ASP. If, for instance,
an user requests a data mining operation that needs minutes or
even hours it is not possible to complete the operation and return
response to the user in reasonable time. The server timeout will
not help as no one will be eager to wait so long for response not
knowing if the operation proceeds successfully and even if it is
acceptable any network breakdown even for a second will waste all
the results of the work. In Active Local Pages this issue is even
bigger - running on a desktop the ASP applications are often
expected to do some tasks typical for the desktop programs or the
developer may want just to implement something in script instead
of using/buying more advanced programming tool (such as C++,VB,
Delphi etc.). In the most cases the critical part of the work is
done by utility COM components - such as ADO, file access objects,
filtering objects etc. This means that the efforts to build this
part of the application with another (often expensive and also
requiring new skills) tool will not pay back. So, implementing the
asynchronous tasks in script if possible is very often the best
balanced solution. Without the threading objects in this library
the developer may use Windows Scripting Host to run a script in a
separate process and get the results, for example, by reading a
file generated by the script. There are also other techniques but
actual improvement will be a component allowing the application to
run a script asynchronously in a separate thread and check the
results from time to time. If it is possible to control the thread
- e.g. start/stop it, present data/messages to the script running
in it (to achieve some dynamic synchronization) - then it will be
even better.
The thread classes in ActiveX pack1 library provide such
features and an ASP application is able to overcome the
limitations of the request-response architecture. In theory the
simplicity of the request/response structure combined with the
ability to perform background tasks and monitor them makes such a
typical WEB development environment almost as powerful as any
desktop programming environment, but still keeps the traits of a
WEB development environment.
Later in this chapter you can see a simple sample on how
the threads are used, but still a few words about real life usage
will give you better view. Soon after implementing the first
version of the COMThread object in our library we have been
involved in two projects where it fitted. One of them was data
mining - it required a lot of data to be extracted and organized
from a data base designed for tasks very different from what we
had to do with it. So, it required a lot of time, creation of
temporary tables and so on. Over the full data base it took more
than 15 minutes! There was no requirement what kind of application
will do it - desktop or WEB. With ALP and a thread to perform the
slow task we implemented it as WEB application for the desktop.
After some time the client decided that the application can be
installed on the WEB server as well and it needed only a few lines
adjustments. In general the application collected the user
preferences, set of complex rules and then it started the
background operation. In the same time the user was able to check
the progress, extract intermediate reports and even influence the
process. The other task was much simpler - collecting data from
the file system. In this case the thread collected/refreshed the
data in a data base and the user was able to use the front end UI
while the background data collection was in progress.
Using threads - example
For simplicity we will use the COMScriptThread object here. Let
use something simple to illustrate it - for example finding the
count of the prime numbers up to a certain number. We will use a
slow script that does this in the worst possible way ;) - for the
example purposes we need a slow operation. So, we will have two scripts - a
controller script (we will use ASP page) and a script that will
run in the thread. We can also implement this with the Micro
Script Host where the place of the ASP controller page will be
taken by a controller plain script. Let's start with the thread's
script - that finds the count of the prime numbers:
We will expect the max number to be specified in the
Context("Max") and we will use Context("Current")
to store the current number we check and also we will increment
the Context("PrimeCount") each time we find a new prime
number:
Function CheckNum(n)
Dim J
For J = 2 To n - 1
If (n Mod J) = 0 Then
CheckNum = False
Exit Function
End If
Next
CheckNum = True
End Function
Dim I
Context("PrimeCount") = 0
For I = 2 To Context("MaxNum")
Context("Current") = I
If CheckNum(I) Then
Context("PrimeCount") = Context("PrimeCount") + 1
End If
Next
The script we have is simple. Remember that the Context collection
is also available through the Value property of the
COMScriptThread object. So the application that created the thread
can check at any time how far the thread has gone.
Let us now write a simple ASP page that starts this thread. We
will omit any formatting and additional processing here:
Set Application("Thread") = Server.CreateObject("newObjects.utilctls.COMScriptThread")
' Copy this to local variable so we can write shorter statements
Set T = Application("Thread")
' Now we need to get the script source. Let us assume it is in the file thread.vbs
Set sf = Server.CreateObject("newObjects.utilctls.SFMain")
Set file = sf.OpenFile(Server.MapPath("thread.vbs")
If T.Start("VBScript",file.ReadText(-2)) Then
' Start is successful
Else
' Error occured (for example compile time error)
Response.Write("Error: " & T.LastError)
End If
Now in another page (or in the same page if it is designed to
be requested with some parameters that allow us to determine
which action to perform) we can check the thread state. For
example we can use code like this:
If Application("Thread").Busy Then
' Let us display the results so far
Response.write("Current number=" & Application("Thread")("Current") & "<BR>")
Response.write("Prime numbers found=" & Application("Thread")("PrimeCount") & "<BR>")
Else
If Application("Thread").Success Then
' Thread has finished its work we can display the results
Response.write("Total Prime numbers found=" & Application("Thread")("PrimeCount") & "<BR>")
Else
' Error - a run time error
Response.Write("Error: " & T.LastError)
End IF
End If
If you have only one thread the above code is almost everything
you need. May be a little more error checking will be needed
depending on the complexity of the work done, but in general this
is the important steps you need to do. After completing everything
you can call Application("Thread").Stop and reuse
the object again - start another script in it etc.
But consider more complex application with many COMScriptObject-s,
with many pages that may want to start/stop threads etc. In such
case you may need to keep additional information with the threads,
you may need even to synchronize the work with them etc. This
applies to COMScriptObject and COMThread objects as well - in both
cases you may want to construct something more complex. So, let us
see what we will need:
Synchronizing the access to the thread.
Sometimes the application may be written in such a manner that
concurrent requests to different pages may lead to usage of the
same thread object. It is quite possible that two or more pages
may want to use the thread for different purposes - for instance
run different scripts. The very first thing you will need to
deal with this is locking the thread object for certain task.
Using the Application.Lock is one of the possible ways to
go, but it locks the entire application and you may have several
thread objects and no need to block all the other work, but only
lock one thread object. You can use one CustomLock
object kept in the Application under name derived from the
thread's name - for example if you keep the thread in
Application("Thread") you can keep the CustomLock for
it in the Application("ThreadLock"). Then any page
that wants to use this thread object may call
Application("ThreadLock").Lock in order to be sure
that it is the only one that works with it at this moment. So
you can lock the object while you are loading/constructing the
script source and call the start method. After that you should
release the lock (Unlock the CustomLock object) and if there are
other pages waiting for it they will be able to see that it is
in use (see Busy and Active properties) and they may try to use
another thread object (if you have many) or tell the user that
the operation cannot be scheduled at this moment.
Using several thread objects for different purposes.
Aside of synchronization discussed above you may need to know
what each thread does. If the tasks you run in them are too
different (different scripts for example) you may need to attach
some kind of labels - you can use again Application variables or
use a Context collection value (for example you can name it
TaskName) to save key names for the tasks.
Thread pooling. In complex application where many
threads are needed and different tasks are done in them
implementing a thread pool will be the best way to go. How to do
this? One simple technique is to keep an array of fixed size in
the Application with one thread object in each element. Then you
can keep another array in another Application variable where you
store indicator values in each element (e.g. free/in use flag).
Then you can implement a function in your ASP page (most likely
in an include file) that finds the first free thread and signs
it as used after returning it. Then you can keep the index of
the thread used in a Session variable and each time a page needs
to work with the thread you can refer to the thread used by
index. So each task can be referred quite easily - by just using
something like Application("Threads")(Session("MyThread"))
and you will be sure that you will never start too many
threads as their number is fixed when the array has been
created.
We want to remind you again that in ASP application you should
take care to limit the number of threads started. So, if you
expect too many of them to be needed better implement some kind of
pooling in order to control the number and their use. The actual
limitation should be decided over the machine resources. Another
concern are the resources the thread are using - for example if
they all use the same files/database, running too much threads may
cause even negative results as they all will race for the same
resources. Sometimes supporting two or more pools - each one used
for the threads working with different set of resources may
be more effective than one pool.
|