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 the New York ColdFusion User Group (May. 2009) with: Mark Drew

Setting Unscoped Variables Inside CFThread In ColdFusion

By Ben Nadel on
Tags: ColdFusion

A few years ago, I looked at the way scoped-variables were treated inside a ColdFusion component when set within the context of a CFThread tag. Since that posting (which was overly complex in retrospect), I've come to love CFScript; and with that love, I've also dropped most of my explicit references to the Variables and Arguments scopes. This has lead to a few "head scratching" moments when setting unscoped variables inside of a CFThread tag.

Each CFThread tag shares the Variables scope of its parent page. And, when you reference unscoped variables within a CFThread tag, ColdFusion will look in the Variables scope for those references (after looking in the thread-local and thread-attributes scopes). If you go to update an unscoped, simple variable within a CFThread tag, however, you do need to provide the "variables." scope; otherwise, ColdFusion will set the new value in the thread-local scope.

NOTE: I am explicitly referring to "simple" variables as updating properties and indices of Structs and Arrays, respectively, does not suffer from this problem.

To demonstrate this, I've create a ColdFusion component that has two private variables, valueA and valueB. These private variables are then both referenced and updated within a CFThread tag:

  • <cfscript>
  •  
  • component
  • output = false
  • hint = "I test unscoped variables in a thread."
  • {
  •  
  • public void function testThread() {
  •  
  • variables.valueA = "initial value A.";
  • variables.valueB = "initial value B.";
  •  
  • thread
  • name = "test-thread"
  • action = "run"
  • {
  •  
  • // "Update" unscoped value inside CFThread.
  • valueA = "unscoped set inside thread [ #valueA# ].";
  •  
  • // "Update" unscoped value inside CFThread - via other
  • // method that is bound to variables scope.
  • variables.otherMethod();
  •  
  • thread.local = duplicate( local );
  •  
  • }
  •  
  • thread action = "join";
  •  
  • // Dump variables and thread scope to see where the "value"
  • // is currently stored.
  •  
  • writeDump(
  • var = variables,
  • label = "Variables Scope"
  • );
  •  
  • writeDump(
  • var = cfthread[ "test-thread" ],
  • label = "Thread"
  • );
  •  
  • }
  •  
  •  
  • private void function otherMethod() {
  •  
  • valueB = "unscoped set inside other method [ #valueB# ].";
  •  
  • }
  •  
  • }
  •  
  • </cfscript>

To make the demo even more exciting, the thread body calls a private ColdFusion component methods, which then updates one of the unscoped variables. And, when we run the above ColdFusion component method - testThread() - we get the following page output:


 
 
 

 
 Setting unscoped variables inside a CFThread tag body in ColdFusion. 
 
 
 

As you can see, both unscoped variables were successfully read from the Variables scope; however, when updated, both unscoped variable updates were stored in the thread-local scope.

As a metaphor, perhaps it's easiest to think about the CFThread tag as using Prototypal inheritance in which its CFThread prototype is the Component's Variables scope. In prototypal inheritance (think JavaScript), you can read simple values in from your prototype object; but, when you go to set a simple value, the value is stored in your local scope. Prototypal inheritance provides asymmetric access patterns, like CFThread.

Anyway, just a minor note to be aware of.




Reader Comments

If you want to write to a scope outside the thread you'll have to use a backdoor to pass in a reference (just remember ACF's idiotic legacy behaviour of passing arrays by copy instead of by reference):
<cfthread action="run" name="LOCAL.myLittleThread" tunnel="#createObject( 'java', 'java.lang.ref.SoftReference' ).init( LOCAL )#">

Reply to this Comment

@Michael,

When it comes to CFThread and Arrays, you're actually fighting two different wars at the same time! On the one hand, Adobe ColdFusion passes arrays by value, not by reference. But, you're also dealing with CFThread attributes - and, ColdFusion performs a deep-copy when you pass something in via the thread attribute :)

I understand why they do it - it helps with race conditions and concurrency and all that stuff. But, on the other hand, it is very frustrating.

What I usually do now is just pass IDs in via the attributes and then reference the variable's scope when I need to perform actions within the thread body.

Something like this (pseudo code):

  • thread name="foo" userid="#userID#" {
  •  
  • var user = userService.getByID( userID );
  •  
  • }

In this case, I am performing a deep-copy of the integer, UserID, which is no big deal; then, I use the variables-scoped service object - userService - to get a a thread-local copy of the object I need to do stuff with.

So far, that's been workout out fairly well.

Reply to this Comment

Hi Ben,

I've always been comfortable not using the variables prefix, and prefer that the unnamed scope works the same way inside a thread, so I came up with this function that runs a function in the context of a component inside a thread by accessing it via "this":

  • void function spawn(String threadName, func) {
  • var functionName = "spawn_" & threadName;
  • this[functionName] = func;
  • thread action = "run" name = "#threadName#" functionName="#functionName#" { evaluate('this.#functionName#()'); }
  • }

Then elsewhere in the component:

  • void function start() {
  • someUnnamedVar = "hello";
  • spawn("thread name", run);
  • }
  •  
  • private void function run() {
  • // I'm in a thread, and the unnamed scope works
  • writeLog(someUnnamedVar);
  • }


Of course this does clutter the "this" namespace, and you wouldn't want to change the function related to the thread name on the fly because of race conditions, but it does work.

Reply to this Comment

@Robin,

The use of "this" is such an interesting case. I think it has to do with the way that "this" is actually implemented. If you dump out the Variables scope, you will notice that "this" is actually a property of the variables scope: "variables.this = [this scope properties]".

That said, I wonder if references to the "this" are really just doing this:

[implicit variables].this.something

... where "this" is, itself, an "unscoped" variable on the this scope.

I vaguely remember a few years ago trying to override the "this" scope from locally within a function:

http://www.bennadel.com/blog/1992-THIS-Is-Just-A-Locally-Scoped-Variable-Inside-Of-ColdFusion-User-Defined-Functions.htm

Anyway, the whole scoping in ColdFusion components is a little funky monkey :)

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.