Advanced COM Comparison of the Thread objects

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.

newObjects Copyright 2001-2006 newObjects [ ]