Working with threads



Multi-threaded applications use several building blocks, including the following:

  • Starting threads in loops

  • Getting information about the thread processing status

  • Displaying thread results

  • Handling thread errors

  • Using database transactions with threads

Starting threads inside loops

Because threads run asynchronously, page level variables can change during thread execution. As a result of this behavior, if you start threads inside a cfloop, and code inside the threads uses the value of the loop iterator (like the index variable, query name, list item), pass the loop iterator to the thread as an attribute.

The following example shows the use of threads inside a loop. It uses an indexed cfloop tag to start five threads. Each thread gets the current loop index value in a threadIndex attribute. The thread adds an array entry with the threadIndex attribute value of the thread and the current value of the page cfloop index, pageIndex. After joining the threads, the page displays the array contents. When you run the example, particularly if you run it multiple times, you see that at the time the thread saves data to the array, the value of pageIndex has incremented past the threadIndex value, and multiple threads often have the same pageIndex value; but the multiple threads always have the correct threadIndex value.

<cfloop index="pageIndex" from="1" to="5"> 
    <cfthread name="thr#pageIndex#" threadIndex="#pageIndex#" action="run"> 
        <cfset Variables.theOutput[threadIndex]="Thread index attribute:" & 
            threadIndex & "&nbsp;&nbsp; Page index value: " & pageIndex> 
    </cfthread> 
</cfloop> 
 
<cfthread action="join" name="thr1,thr2,thr3,thr4,thr5" timeout=2000/> 
 
<cfloop index="j" from="1" to="5"> 
    <cfoutput>#theOutput[j]# <br /></cfoutput> 
</cfloop>

Using the thread status

The Thread scope status metadata variable lets the page, or any other thread started by the page, determine the status of any thread. The page processing code can then take a necessary action, for example, if the thread has terminated abnormally or has hung. The status variable can have the following values:

Value

Meaning

NOT_STARTED

The thread has been queued but is not processing yet.

RUNNNG

The thread is running normally.

TERMINATED

The thread stopped running as a result of one of the following actions:

  • A cfthread tag with a terminate action stopped the thread.

  • An error occurred in the thread that caused it to terminate.

  • A ColdFusion administrator stopped the thread from the Server Monitor.

COMPLETED

The thread ended normally.

WAITING

The thread has run a cfthread tag with action="join", and one or more of the threads being joined have not yet completed.

Applications can check the thread status to manage processing. For example, an application that requires results from a thread specifies a time-out when it joins the thread; in this case, it can check for the COMPLETED status to ensure that the thread has completed processing and the join did not just result from a time-out. Similarly, an application can check the status value of threads that might not start or might not complete normally, and terminate it if necessary. The example in Ending a thread checks thread status and terminates any threads with RUNNING or NOT_STARTED status.

Handling thread output

To prevent conflicts, only the page thread displays output. Therefore, named threads have the following limitations:

  • ColdFusion places all output that you generate inside a thread, such as HTML and plain text, or the generated output of a cfoutput tag, in the Thread scope output metadata variable. The page-level code can display the contents of this variable by accessing the threadName.output variable.

  • All tags and tag actions that directly send output to the client (instead of generating page text such as HTML output), do not work inside the thread. For example, to use the cfdocument or cfreport tags in a thread, specify a filename attribute; to use a cfpresentation tag, use a directory attribute.

Handling ColdFusion thread errors

If an error occurs in a thread, page-level processing is not affected, and ColdFusion does not generate an error message. If you do not handle the error by using a try/catch block in the thread code, the thread with the error terminates and the page-level code or other threads can get the error information from the thread metadata Error variable and handle the error appropriately.

You cannot use page- or application-based error handling techniques to manage errors that occur during thread execution. For that reason, you cannot use the cferror tag or the onError application event handler for thread errors. Instead, use either of the following techniques:

  1. Use cftry/cfcatch tags or try/catch CFScript statements in the cfthread body to handle the errors inside the thread.

  2. Handle the error outside the thread by using the thread error information that is available to the page and other threads in the Thread scope threadName.Error variable. Application code can check this variable for error information. For example, after you join to a thread that had an error, you could check the threadname.status variable for a value of terminated, which indicates that the thread terminated abnormally. You could then check the threadName.Error variable for information on the termination cause.

Handling database transactions

Database transactions cannot span threads. For example, consider a page with the following structure:

<cftransaction> 
    <cfthread name ="t1" ...> 
        <cfquery name="q1" ...> 
            ... 
        </cfquery> 
    </cfthread> 
    <cfquery name="q2" ...> 
        ... 
    </cfquery> 
    <cfthread action="join" name="t1" ... /> 
</cftransaction>

In this case, query q1 is not included in the transaction that contains query q2. To include both queries in the transaction, you must place the complete transaction in a single thread, using a structure such as the following:

<cfthread name ="t1" ...> 
    <cftransaction> 
        <cfquery name="q1" ...> 
            ... 
        </cfquery> 
        <cfquery name="q2" ...> 
            ... 
        </cfquery> 
    </cftransaction> 
</cfthread> 
<cfthread action="join" name="t1" ... />