THREAD CREATE statement

Purpose | Create a Windows thread, which is a smaller "program-within-a-program", that runs concurrently with the main thread and other threads in the same application program. Threads provide powerful ways for an application to perform several tasks at the same time.
Syntax | THREAD CREATE FuncName(param) [StackSize,] [SUSPEND] TO hThread

Remarks

THREAD CREATE creates and begins execution of a new thread Function identified by FuncName. FuncName is specified without quotation marks. This function must take exactly one Long-integer or Double-word (DWORD) parameter by value ( BYVAL). For example:

THREAD FUNCTION MyThreadFunction(BYVAL x AS LONG) AS LONG

' Thread code goes here

END FUNCTION

' more code here

THREAD CREATE MyThreadFunction(var&) TO hThread???

The 32-bit parameter passed to the thread may be used to pass a value such as a programmer-defined ID or window handle to post "progress" messages back to a GUI window/dialog running in another thread. A more common use for the parameter is to pass the address to a UDT or other data structure. Passing an address this way can enable the thread to use a pointer to access large volumes of data that reside outside of the thread. For example:

THREAD FUNCTION MyThread(BYVAL y AS DWORD) AS DWORD

DIM x AS MyUDT POINTER

x = y ' Set the pointer from the DWORD param

' From here we can access all of the UDT member elements

' using the standard @x pointer syntax

END FUNCTION

' more code here

DIM x AS MyUDT, hThread???

' Initialize the members of x here

THREAD CREATE MyThread( VARPTR(x) ) TO hThread???

' more code here

Note that data passed this way is subject to the notes (below) concerning GLOBAL and STATIC variables, in order to avoid synchronization problems during context-switching.

The return value of the thread Function is retrieved with the THREAD STATUS statement (once the thread has completed execution).

StackSize | A long integer expression to specify the requested size of the stack for this newly created thread. This value should always be specified in increments of 64K (65536). If this parameter is omitted, the size of the stack for the main thread will be used.
SUSPEND | Execution of the thread begins immediately unless the SUSPEND option is included. In that case, the suspend count for the thread will be initially set to 1, and the thread will be initially suspended. The THREAD RESUME statement is used to decrease the suspend count of a thread by 1, and when the suspend count reaches 0, the thread will start (resume) execution. Controlling the suspend state of a thread requires the thread handle value be retained until such time as the thread can be closed or left to run unmonitored.
hThread | If successful, THREAD CREATE returns a Double-word (or Long-integer) handle in hThread, or zero (0) if the thread was not started. This handle is used with the other thread statements to control the suspend count, and to release the thread handle, etc. Also see THREAD CLOSE for more information on monitoring, closing, and waiting for threads to complete.
FuncName | The name of the thread function to execute as a thread. A thread Function must comply exactly with the following syntax: THREAD FUNCTION ThreadFuncName (BYVAL param AS {LONG | DWORD}) AS {LONG | DWORD}

Restrictions

The THREAD CREATE statement generates no run-time errors; all exceptions are reported as a zero stored in the return value hThread. However, the target thread Function must be located in the same compiled module as the THREAD CREATE statement. That is, a thread Function may not be an imported Function.

Additionally, a thread Function may not be directly called or executed, except by a THREAD CREATE statement. This restriction is imposed to ensure that PowerBASIC run-time library can maintain a thread-safe state at all times, correctly allocate and deallocate internal thread-local storage, and the various thread functions (such as THREADCOUNT) can return accurate values.

One situation that can arise is where a Function may need to be invoked both directly and used as a thread Function. The easiest solution is to create a small wrapper Function for the Function, then use THREAD CREATE with the wrapper Function when a thread is required, or continue to call the original Function directly when a separate thread is not required. For example:

FUNCTION WorkerFunc(BYVAL x AS LONG) AS LONG

' code here

END FUNCTION

THREAD FUNCTION WorkerThread(BYVAL x AS LONG) AS LONG

FUNCTION = WorkerFunc(x)

END FUNCTION

' more code here

' Execute the worker function directly, thus:

lResult& = WorkerFunc(var&)

' Execute the worker thread as a thread, using

' the wrapper function:

THREAD CREATE WorkerThread(var&) TO hThread???

A thread can determine its own ID with the THREADID function. Note: a thread ID is not interchangeable with a thread handle.

Threads are initialized and started asynchronously, so it is wise to give the operating system a small amount of time to perform thread initialization before using the THREADCOUNT function to monitor the thread.

Once a thread has exited, it is not possible to restart the same thread as identified by hThread - however, a new thread can be initiated using the same Function (which naturally provides a new hThread handle value). In addition, the same thread Function can be launched multiple times to create a set of identical threads executing the same code.

As each thread is created, it is assigned its own "private" stack frame. Therefore, LOCAL and REGISTER variables are private to each thread, and are automatically "thread-safe".

Exercise care when using GLOBAL and STATIC variables that may be accessed by more than one thread at the same time. If one thread is part way through storing data at the point where another thread begins to read the same memory block, it can result in the second thread reading only partially updated (i.e., invalid) data. The point where one thread is suspended so that another can run is called a "context-switch". In these situations, the use of Windows' synchronization functions (such as Critical Sections and Mutexes) may be employed to create thread-safe code.

Thread-safe code is deemed to be unaffected by context-switching, regardless of when context-switching occurs. Local variables, being stored in a "private" stack frame, are not affected by context-switching.

Local variable storage created by each thread is automatically freed when the thread Function terminates, in the same manner as a normal Sub, Function, Method, or Property. However, the thread handle must be explicitly freed with a THREAD CLOSE statement. The THREAD CLOSE can occur at any time, since it only frees the thread handle and has no other impact on the running thread. If the thread result value is not required (or the thread state does not need to be altered), THREAD CLOSE can be used immediately after the THREAD CREATE statement, leaving the thread to run its course.

For more information on threading and synchronization techniques, please refer to MSDN http://msdn.microsoft.com.

The PowerBASIC run-time library is thread-safe and reentrant.

See also | FUNCTION/END FUNCTION, THREAD CLOSE, THREAD Code Group, THREAD GET PRIORITY, THREAD Object, THREAD RESUME, THREAD STATUS, THREAD SUSPEND, THREADCOUNT, THREADED, THREADID

Example

SUB SpawnThreads()

LOCAL x AS LONG

LOCAL s AS LONG

DIM hThread(10) AS LOCAL DWORD

FOR x = 1 TO 10

THREAD CREATE MyThread(x) TO hThread(x)

SLEEP 50

NEXT

DisplayText "10 Threads Started! " + _

"Wait for them to finish!"

DO

FOR x = 1 TO 10

SLEEP 0

THREAD STATUS hThread(x) TO s

IF s <> &H103 AND s <> 0 THEN ITERATE DO

NEXT

LOOP WHILE s

FOR x = 1 TO 10

THREAD CLOSE hThread(x) TO s

NEXT x

DisplayText "Finished!"

END SUB

' The following is executed as a thread Function!

THREAD FUNCTION MyThread (BYVAL x AS LONG) AS LONG

LOCAL n AS LONG

LOCAL t AS SINGLE

DisplayText "Begin Thread" + STR$(x)

t = TIMER

FOR n = 1 TO 10

SLEEP 100 + 100 * x

NEXT n

t = TIMER - t

DisplayText "End Thread" + STR$(x) + _

" Elapsed time = " + STR$(t,5)

END FUNCTION