Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with:

Scope Behavior When Using CFThread Inside Of ColdFusion Components

By Ben Nadel on
Tags: ColdFusion

The other day, I was using CFThread to call a ColdFusion component method asynchronously. Typically, when I use CFThread, I define my CFThread tags in the controller. In this case, however, I was making the asynchronous call from within the context of the component itself. This works very well. But, but there are some minor variances in scope-behavior that depend on how the asynchronous method is invoked.

Before we get into the CFThread / scope interaction caveats, however, I thought I'd show a simple scenario that demonstrates how to use CFThread within the context of a ColdFusion component instance. In this demo, I have a Girl.cfc component that takes a remote URL as one of its constructor arguments. This URL points to the headshot (photo) of the Girl which will be download asynchronously once the Girl class has been instantiated.

Girl.cfc

  • <cfcomponent
  • output="false"
  • hint="I am a girl.">
  •  
  • <!---
  • Create a unique ID for this component instance. All threads
  • run in a single page request need to be unique - this will
  • help us name our threads so as not to conflict with other
  • CFC instances.
  • --->
  • <cfset variables.hash = createUUID() />
  •  
  •  
  • <cffunction
  • name="init"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I initialize this component.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="name"
  • type="string"
  • required="true"
  • hint="I am the girl name."
  • />
  •  
  • <cfargument
  • name="headshotUrl"
  • type="string"
  • required="true"
  • hint="I am the URL for the headshot photo."
  • />
  •  
  • <!--- Store the properties. --->
  • <cfset variables.name = arguments.name />
  • <cfset variables.headshotUrl = arguments.headshotUrl />
  • <cfset variables.headshot = "" />
  •  
  • <!---
  • Because downloading the actual headshot might take a
  • while, we want to run the download process asynchronously
  • to the rest of the component. Start process in its own
  • thread.
  •  
  • NOTE: The use of variables.hash is to ensure that this
  • thread is uniquely named in the context of the current
  • page request.
  • --->
  • <cfthread
  • name="downloadHeadshot-#variables.hash#"
  • action="run">
  •  
  • <!--- Invoke donwload method. --->
  • <cfset this.downloadHeadshot() />
  •  
  • </cfthread>
  •  
  • <!--- Return this object reference. --->
  • <cfreturn this />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="downloadHeadshot"
  • access="public"
  • returntype="void"
  • output="true"
  • hint="I download the headshot.">
  •  
  • <!--- Download image from the given URL. --->
  • <cfimage
  • name="variables.headshot"
  • action="read"
  • source="#variables.headshotUrl#"
  • />
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="getHeadshot"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I return the headshot property (NOTE: May not be a valid ColdFusion image yet as the image downloads asynchronously).">
  •  
  • <cfreturn variables.headshot />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="getName"
  • access="public"
  • returntype="string"
  • output="false"
  • hint="I return the name property.">
  •  
  • <cfreturn variables.name />
  • </cffunction>
  •  
  • </cfcomponent>

As you can see here, the downloadHeadshot() class method is being called asynchronously (ie. inside of a CFThread tag) from within the Girl.cfc init() method. When using the CFThread tag from within a ColdFusion component, it is a good practice to include some instance-specific value in the Name attribute of the CFThread tag. Since all CFThread tags initiated in the context of a single page request must have unique names, including some sort of unique ID allows two CFC instances to run the same thread on the same request without error.

To test this ColdFusion component / CFThread composition, I wrote a script that instantiates two Girl.cfc instances and outputs their value:

  • <!--- Create a girl instance. --->
  • <cfset tricia = createObject( "component", "Girl" ).init(
  • "Tricia",
  • "http://farm5.static.flickr.com/4154/4948350227_0d3995e4c8_b.jpg"
  • ) />
  •  
  • <!--- Create a girl instance. --->
  • <cfset sarah = createObject( "component", "Girl" ).init(
  • "Sarah",
  • "http://farm5.static.flickr.com/4151/4946057759_5a49aa7fff.jpg"
  • ) />
  •  
  •  
  • <!---
  • Because the headshots are downloading asynchronously, let's
  • sleep the current page thread to let those download before
  • we can test them.
  • --->
  • <cfthread
  • action="sleep"
  • duration="#(5 * 1000)#"
  • />
  •  
  •  
  • <!---
  • Output the Girl instance data.
  •  
  • NOTE: I am use base64-encoded data urls because my ColdFusion
  • image manipulation is broken - I did something to mess up the
  • JARs or somethign?? Typically, I would use
  • CFImage[ writeToBrowser ] to demo these images.
  • --->
  • <cfoutput>
  •  
  • <div style="float: left ; padding-right: 20px ;">
  •  
  • <strong>Name:</strong> #tricia.getName()#
  •  
  • <br />
  • <br />
  •  
  • <img
  • src="data:image/*;base64,#toBase64( tricia.getHeadshot() )#"
  • height="325"
  • />
  •  
  • </div>
  •  
  • <div style="float: left ;">
  •  
  • <strong>Name:</strong> #sarah.getName()#
  •  
  • <br />
  • <br />
  •  
  • <img
  • src="data:image/*;base64,#toBase64( sarah.getHeadshot() )#"
  • height="325"
  • />
  •  
  • </div>
  •  
  • </cfoutput>

As you can see here, each Girl.cfc instance gets its own name and remote headshot URL. Again, because both of these Girl.cfc instances are invoking the same thread functionality (within their own context), it is critical that each of those CFThread tags is uniquely named. Failure to do so will result in the following ColdFusion error:

Attribute validation error for the cfthread tag. Thread with name DOWNLOADHEADSHOT could not be created. Thread names must be unique within a page.

Since I am including a "variables.hash" UUID in the CFThread names, however, this page runs without a problem and produces the following page output:

 
 
 
 
 
 
CFThread Being Used In The Context Of A ColdFusion Component In Order To Call Class Methods Asynchronously. 
 
 
 

Now that we see generally how to use CFThread within the context a ColdFusion component, let's explore the CFThread behavior a little deeper. In the above example, I am using the THIS scope to invoke the downloadHeadshot() class method:

  • <!--- Invoke download method. --->
  • <cfset this.downloadHeadshot() />

In a ColdFusion component, there are three ways to invoke public methods:

  • This-scoped (ie. this.downloadHeadshot())
  • Variables-scoped (ie. variables.downloadHeadshot())
  • Non-scoped (ie. downloadHeadshot())

When it comes to invoking a class method from within a CFThread tag, the style in which we invoke the class method subtly influences the scope behavior within the invoked method's body. To demonstrate this, I have created another ColdFusion component that uses all three invocation approaches in the context of a CFThread tag:

Test.cfc

  • <cfcomponent
  • output="false"
  • hint="I am test component for thread-based method invocation.">
  •  
  • <cffunction
  • name="init"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I initialize this component.">
  •  
  • <!--- Set up the log file path. --->
  • <cfset this.logPath = expandPath( "./log.htm" ) />
  •  
  • <!--- Create a new log file. --->
  • <cfset fileWrite( this.logPath, "" ) />
  •  
  •  
  • <!--- --------------------------------------------- --->
  • <!--- --------------------------------------------- --->
  •  
  • <!--- Define some variables for testing. --->
  • <cfset variables.value = "Variables" />
  • <cfset this.value = "This" />
  •  
  • <!--- Call thread with THIS scoping. --->
  • <cfthread
  • name="scopeTest1"
  • action="run">
  •  
  • <cfset this.testThread() />
  •  
  • </cfthread>
  •  
  • <!--- Join the thread. --->
  • <cfthread action="join" />
  •  
  • <!--- Log thread and component information. --->
  • <cfdump
  • var="#scopeTest1#"
  • label="THIS-Scoped Thread"
  • output="#this.logPath#"
  • format="html"
  • />
  •  
  • <!--- --------------------------------------------- --->
  • <!--- --------------------------------------------- --->
  •  
  • <!--- Define some variables for testing. --->
  • <cfset variables.value = "Variables" />
  • <cfset this.value = "This" />
  •  
  • <!--- Call thread with VARIABLES scoping. --->
  • <cfthread
  • name="scopeTest2"
  • action="run">
  •  
  • <cfset variables.testThread() />
  •  
  • </cfthread>
  •  
  • <!--- Join the thread. --->
  • <cfthread action="join" />
  •  
  • <!--- Log thread and component information. --->
  • <cfdump
  • var="#scopeTest2#"
  • label="VARIABLES-Scoped Thread"
  • output="#this.logPath#"
  • format="html"
  • />
  •  
  • <!--- --------------------------------------------- --->
  • <!--- --------------------------------------------- --->
  •  
  • <!--- Define some variables for testing. --->
  • <cfset variables.value = "Variables" />
  • <cfset this.value = "This" />
  •  
  • <!--- Call thread with NO scoping. --->
  • <cfthread
  • name="scopeTest3"
  • action="run">
  •  
  • <cfset testThread() />
  •  
  • </cfthread>
  •  
  • <!--- Join the thread. --->
  • <cfthread action="join" />
  •  
  • <!--- Log thread and component information. --->
  • <cfdump
  • var="#scopeTest3#"
  • label="NON-Scoped Thread"
  • output="#this.logPath#"
  • format="html"
  • />
  •  
  • <!--- Return this object reference. --->
  • <cfreturn this />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="testThread"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="I am called inside of a thread to test scoping.">
  •  
  • <!--- Define the local scope. --->
  • <cfset var local = {} />
  •  
  • <!---
  • Test "read" actions from all thread scope situations:
  •  
  • - Non-scoped (ie. variables)
  • - This-scoped
  • - Variables-scoped
  • --->
  • <cfset thread.value = value />
  • <cfset thread.thisValue = this.value />
  • <cfset thread.variablesValue = variables.value />
  •  
  • <!--- Test "write" for non-scoped. --->
  • <cfset value = "thread" />
  •  
  • <!--- Log three scopes again. --->
  • <cfset thread.value_2 = value />
  • <cfset thread.thisValue_2 = this.value />
  • <cfset thread.variablesValue_2 = variables.value />
  •  
  • <!--- Test "write" for this-scoped. --->
  • <cfset this.value = "thread" />
  •  
  • <!--- Log three scopes again. --->
  • <cfset thread.value_3 = value />
  • <cfset thread.thisValue_3 = this.value />
  • <cfset thread.variablesValue_3 = variables.value />
  •  
  • <!--- Test "write" for variables-scoped. --->
  • <cfset variables.value = "thread" />
  •  
  • <!--- Log three scopes again. --->
  • <cfset thread.value_4 = value />
  • <cfset thread.thisValue_4 = this.value />
  • <cfset thread.variablesValue_4 = variables.value />
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  • </cfcomponent>

This ColdFusion component is kind of a lot to take in, so I'll try to break it down as simply as possible. The init() method of the component launched three different threads, each of which invokes the class method testThread() in one of the three possible invocation approaches. After each thread is launched, I wait for it to rejoin the current thread before I CFDump the thread data to a log file.

Other than the three calls to testThread(), the init() method is not that interesting. The real exploration takes place in the testThread() body. Within testThread(), I am reading from and writing to the "three" scopes (ie. unscoped, this, variables) and storing their values in the Thread scope. The Thread scope then makes those values available to the calling context by way of the Thread instance.

Of all of the read/write tests that I am performing here, the only really interesting one is the unscoped-value test. In this, we are looking at how ColdFusion sets unscoped values within the context of a CFThread tag that is, itself, in the context of a ColdFusion component. This is particularly interesting because CFThread definitely has its own local scope that can store private variables.

When we invoke this Test.cfc ColdFusion component and run the init() method, we end up with the following log file:

 
 
 
 
 
 
Using CFThread Within The Context Of A ColdFusion Component Can Change The Scope-Traversal Behavior. 
 
 
 

Most of the data here is a result of the expected behavior; but, look at the "variables" references - This-scoped method invocation results in a different read/write outcome than that of both the Variables-scoped and the Non-scoped method invocations.

This outcome is a result of this code:

  • <!--- Test "write" for non-scoped. --->
  • <cfset value = "thread" />

When this line of code is run in either the Variables-scoped or the Non-scoped CFThread context, the CFSet writes the value to the CFThread's local scope. However, when the testThread() method is invoked on the This scope (ie. this.testThread()), the above line of code writes the value to the ColdFusion component's Variables scope.

Another thing to notice here is that when the CFSet tag references a scoped-variable, the CFSet behavior is uniform across all three method invocation approaches. Moral of that story: always scope your variables! Not only does it create self-documenting code, it also ensures more consistent behavior.

ColdFusion's CFThread tag is awesome, but complex in its subtleties; behind the scenes, the CFThread tag executes as a ColdFusion function, complete with its own Arguments scope and Local scope. This can allow for some very interesting algorithms; but, it can also lead to some problematic conflicts. When you use CFThread, especially within a ColdFusion component, just take a little extra time to think about how the CFThread context will interact with its calling context.




Reader Comments

Thanks Ben,
Excellent article and very precise explanation.

Cheers

Philip

A question on invoking asynchronous save or some task and returning response back to the calling page.

Using cfThread is the right way to do it.
The scenario is, I would like to call some audit trail in the page and don't want to wait for success/ failure and process the rest of the page. To accomplish this scenario, I need to call the component which has the insert/ update query in thread and this would free my request call, right?

@Philip,

If I understand you correctly - that you want to process something without having to wait for the processing to finish - then yes, putting that processing inside a CFThread tag will free your page up. CFThread is compiled down to a function and then run in parallel to the current page. When you do this, just be careful how you pass data to the CFThread tag. If you use the attribute, data is copied by deep-copy. This includes CFC references.

Thanks Ben,

One more question on this:
If in the method I have CFThread and in there cfquery tag in the thread, still this will proceed and finish the call?

Secondly, If inside thread I have a call to another method (or component) then the parameters will be arguments and this will still works.

Third case, if I call a cfmodule then you said it would pass the data after duplicating it (deep copy), that should be fine, the thing I didn't get it, you mentioned CFC references, how and what effect this would have?

Thanks

Philip

@Philip,

I am sorry, I don't really understand what you are asking. Anything that you do inside of the CFThread tag will happen asynchronously - this includes other method calls, cfquery tags, and cfmodule calls.

If you want to get a value OUT of the CFThread tag, you typically need to join it back with a CFThread[action=join] call.