THREAD Object  

Purpose

A thread is a "program-within-a-program", that runs concurrently with the main thread and other threads in a single application program. Threads provide powerful ways for an application to perform several tasks at the same time.  When executed on a computer with a multi-core CPU, threads can improve performance to a remarkable level.

THREAD objects offer a collection of methods which allow you to easily create and maintain additional threads of execution in your programs.

A thread can be completely encapsulated (contained) within a thread object.  Encapsulation makes an object the perfect vehicle to host a thread.  With thread objects, you'll have easy access to multiple thread parameters, private methods, and thread local storage of data. In short, a complete program-within-a-program which can be executed with ease.

We liken this to the concept that "Threads are Alive".  When a thread object is created and launched, it takes on a life of its own.  It lives (and executes) until its lifetime is over and the thread ends. The life of the thread parallels the life of the object which makes it quite easy to manage.

PowerBASIC provides a pre-defined interface named "IPowerThread", which is a DUAL interface (Dispatch and direct access).  When you create a thread object, you first inherit IPowerThread, giving you immediate access to all of its member methods. Next, you add a THREAD METHOD, a special form of private CLASS METHOD, which is automatically executed when the thread is launched.

It's important to remember that the THREAD METHOD you create contains the code which will be executed in the thread.  When you start the thread (by calling the LAUNCH method), it executes your THREAD METHOD. When you reach the end of the THREAD METHOD, the thread ends, and its lifetime is over.  The THREAD METHOD acts just like the MAIN (or PBMAIN) function in your executable.

You may give the THREAD METHOD any name you wish.  However, it is recommended you name it MAIN or PBMAIN.  This bit of self-documentation will be a simple reminder of the functionality when you review the code a year from now!  Generally speaking, most thread objects consist primarily of CLASS METHODS which are called from the THREAD METHOD. If there are any Member Methods (visible from outside the class), they are not usually called from within the thread.  Instead, they are typically called from other threads to monitor the status and progress.

There must be exactly one THREAD METHOD per Class.  No more.  No less. The THREAD METHOD is executed automatically; it may never be called from within your program.

Instance variables are declared just as in any other class.  Unique parameters are passed to each object when it is launched.  Finally, public methods and properties may be added to monitor and manipulate the life of your thread.

Here's a synopsis of THREAD OBJECT usage:

  1. Create a class with an interface which inherits IPowerThread.

  2. Create a THREAD METHOD, best named MAIN or PBMAIN.

  3. Create an INSTANCE variable named THREADPARAM which will hold the parameter(s) you choose to pass to the thread when it begins execution.  This is usually another object variable.

  4. Create CLASS METHODS as needed, which will be called from the THREAD METHOD for support of that code.

  5. From the main thread, create an object variable of the thread class and interface.

  6. Call the LAUNCH method, passing the appropriate parameter to be used as THREADPARAM.  Your thread is now running and alive.

Syntax

<ObjectVar>.membername(params)

RetVal = <ObjectVar>.membername(params)

<ObjectVar>.membername(params) TO ReturnVariable

Remarks

With the advent of multi-core CPU's and multi-CPU computers, it's clearly desirable to encapsulate all of the information about a particular thread in a single component.  We recommend that all new code use THREAD OBJECTS exclusively, rather than the Thread Code Group. Thread objects provide much greater control, and much better thread parameter handling for the programmer.

IPowerThread Methods

The Dispatch ID (DispID) for each member method is displayed within angle brackets.

METHOD CLOSE() <2>

Releases the thread handle of this thread.  Note that it does not stop a thread if it is still running; it simply releases the thread handle (i.e., the resources used to track the thread).

Thread handles should not be released until there is no further need to use other thread methods or properties.  If a thread does not need to be monitored, its handle can be released immediately.  The thread resources will be freed automatically when the thread terminates naturally.

THREADCOUNT continues to report a thread tally that will include threads whose handle has already been released.  A thread ID value may not be used interchangeably with a thread handle value.

METHOD EQUALS(ObjectVar AS InterfaceName) AS Long  <3>

Compares the parameter ObjectVar to determine if it references the same object as this object.  If they both reference the same object, true (-1) is returned; if not, false (0) is returned.

METHOD HANDLE() AS Long <4>

Retrieves the handle of the thread for use with Windows API functions.

METHOD ID() AS Long <5>

Retrieves the ID of the thread for use with Windows API functions.

METHOD ISALIVE() AS Long <6>

Checks the thread to see if it is currently "alive".  If the thread has been launched, but has not yet ended, the value true (-1) is returned; if not, the value false (0) is returned.

METHOD JOIN(ThreadObjectVar AS InterfaceName, TimeOutVal AS Long) <7>

Waits for the thread referenced by ThreadObjectVar to complete before execution of this thread continues.  TimeOutVal specifies the maximum length of time to wait, in MilliSeconds.  If TimeOutVal is zero (0), the time to wait is infinite.

METHOD LAUNCH(ByRef Param as UDT) <8>

LAUNCH begins execution of the thread, passing parameter data to it. Since the thread is hosted by an object, it is only fitting that the parameter data be contained in the most robust form, another object.

THREADPARAM is a mandatory Instance variable which you must define in each thread class.  It is normally declared as the interface name of your choice:

INSTANCE ThreadParam as MyInterface

When the thread begins, PowerBASIC automatically creates a copy of the LAUNCH parameter, and assigns it to ThreadParam.  Since it is stored in an Instance variable, it is visible to all of your code in your member methods, yet is kept private from the rest of the program. The use of an object as the parameter is the normally the best choice, as it allows virtually any number of data items to be contained.

In simpler cases, you may choose to declare THREADPARAM as a Pointer, Long Integer, or Dword.  In that case, you must pass the launch parameter using a BYVAL option, to override the expected object variable.

INSTANCE ThreadParam as LONG

...

MyThread.Launch(ByVal MyNumber&)

Of course, the Pointer parameter option can be used to pass a pointer to any variable, of any type.  For example, it could be used to pass a used-defined type if that fits your needs:

INSTANCE ThreadParam AS MyType POINTER

 

 THREAD METHOD MyMethod() AS LONG

   xyz# = ThreadParam.member1

   ... other code

END METHOD

...

MyThread.Launch(ByVal VARPTR(MyType))

PROPERTY GET PRIORITY() AS Long <9>

Retrieves the priority value for this thread.  The thread priority value is one of the following:

%THREAD_PRIORITY_IDLE         = -15

%THREAD_PRIORITY_LOWEST       =  -2

%THREAD_PRIORITY_BELOW_NORMAL =  -1

%THREAD_PRIORITY_NORMAL       =   0

%THREAD_PRIORITY_ABOVE_NORMAL =  +1

%THREAD_PRIORITY_HIGHEST      =  +2

%THREAD_PRIORITY_TIME_CRITICAL= +15

PROPERTY SET PRIORITY (LEVEL AS Long) <9>

Sets the Priority Value for this thread.  The thread priority value must be one of the following:

%THREAD_PRIORITY_IDLE         = -15

%THREAD_PRIORITY_LOWEST       =  -2

%THREAD_PRIORITY_BELOW_NORMAL =  -1

%THREAD_PRIORITY_NORMAL       =   0

%THREAD_PRIORITY_ABOVE_NORMAL =  +1

%THREAD_PRIORITY_HIGHEST      =  +2

%THREAD_PRIORITY_TIME_CRITICAL= +15

METHOD RESULT() AS Long <10>

If the thread has ended, the result value returned by the THREAD METHOD is retrieved and returned to the caller.  The result may be any integral value in the range of a long integer.  However, you should avoid using the number &H103 (decimal 259), as that is the value used by Windows to signify that the thread is still running.

If the result is retrieved successfully, the OBJRESULT is set to %S_OK (0).  If the thread has not ended, the value zero (0) is returned, and the OBJRESULT is set to %S_FALSE (1).

METHOD RESUME() AS Long <11>

Resumes execution of a suspended thread.  The suspend count of the thread is decremented.  When it reaches zero (0), execution of the thread resumes.  If the resume is successful, the prior suspend count is returned; otherwise, -1 is returned.

A thread can suspend itself with SUSPEND (which increments the suspend count), but logically, cannot RESUME itself because it is not running at that time.

PROPERTY GET STACKSIZE() AS Long <13>

Retrieves the size of the stack for this thread.  If the value returned is zero (0), the thread StackSize is the same as that of the main thread.

PROPERTY SET STACKSIZE(Long) <13>

Sets the size of the stack for this thread to the value specified by the parameter.  The value should always be specified in multiples of 64K (65536).  PROPERTY SET must only be executed prior to thread execution with LAUNCH, or it will be ignored.  If no PROPERTY SET STACKSIZE is executed, the size of the stack for the main thread will be used for this thread.

METHOD SUSPEND() AS Long <14>

Suspends execution of the thread.  The suspend count of the thread is incremented.  If the suspend was successful, the suspend count is returned; otherwise, -1 is returned.

If SUSPEND is executed prior to LAUNCH of the thread, the suspend count is incremented, and the subsequent LAUNCH is treated as a suspended launch.  That is, all the necessary setup tasks are performed, but the thread is suspended just before execution of your THREAD METHOD begins. You can continue execution with RESUME.

A thread can suspend itself with SUSPEND (which increments the suspend count), but logically, cannot RESUME itself because it is not running while suspended.

METHOD TIMECREATE() AS Quad <16>

Retrieves the date and time-of-day of the thread creation, and returns it as a Quad Integer value.  The internal format of the value is that of a FILETIME structure, so you can use the PowerTime object to convert it to a human readable format of Month/Day/Year/Time.

METHOD TIMEEXIT() AS Quad <17>

Retrieves the date and time-of-day of the thread exit, and returns it as a Quad Integer value.  The internal format of the value is that of a FILETIME structure, so you can use the PowerTime object to convert it to a human readable format of Month/Day/Year/Time.  If the thread has not yet exited, the return value is undefined.

METHOD TIMEKERNEL() AS Quad <18>

Retrieves the amount of time this thread has spent in kernel mode, and returns it as a Quad Integer value.  The internal format of the value is that of a FILETIME structure, so you can use the PowerTime object to convert it to a human readable format.

METHOD TIMEUSER() AS Quad <19>

Retrieves the amount of time this thread has spent in user mode, and returns it as a Quad Integer value.  The internal format of the value is that of a FILETIME structure, so you can use the PowerTime object to convert it to a human readable format.

Restrictions

Functions from the Thread Code Group and THREAD OBJECTS may co-exist in the same application.  However, it is important that they not be intermixed when you reference one particular thread.

See also

PowerTime, THREAD Code Group

Example

CLASS MyClass

  INSTANCE ThreadParam as DataFace

 

  THREAD METHOD MAIN() AS LONG

    x& = ThreadParam.GetANumber()

    CON.PRINT DEC$(x&)

  END METHOD

 

  INTERFACE MyFace

    INHERIT IPOWERTHREAD

      

    METHOD abc

      END METHOD

    END INTERFACE

END CLASS

 

CLASS DataClass

  INTERFACE DataFace

    INHERIT DUAL

    

    METHOD GetANumber() AS LONG

      METHOD = 77

    END METHOD

 

  END INTERFACE

END CLASS

 

FUNCTION PBMain()

  LOCAL xx AS MyFace

  LET xx = CLASS "MyClass"

  

  LOCAL oo AS DataFace

  LET oo = CLASS "DataClass"

 

  xx.launch(oo)

  xx.join(xx, 0)

END FUNCTION