Learning ColdFusion 8: CFThread Part IV - Cross-Page Thread References

Posted June 4, 2007 at 6:44 PM

Tags: ColdFusion

Up until now, we have been examining ColdFusion 8's CFThread tag in the context of a single page or in conjunction with a "set it and forget it" scenario. Now, let's take a look at referencing long running threads across page requests. Remember, since the child thread launched by CFThread may outlive the processing time of its parent, we will have the opportunity to reference a thread that was launched by a previous page request.

To play around with this, we are going to modify our previous Flickr.com photo download demo to use some AJAX. Now, instead of just display the "photos are downloading" message to the user on the confirmation page, we are going to output the status of each photo thread as it updates. This means that after the parent page has finished processing (the confirmation page), we are going to be referencing threads launched by a previous page request. Very exciting.

Before I get into the code, I want to take a second to talk about the THREAD scope. In the demo below, we are telling each CFThread-launched thread to both store itself and then remove itself from the APPLICATION.Threads structure. In doing so, you will notice that I am actually duplicating the THREAD scope within each CFThread tag:

 Launch code in new window » Download code as text file »

  • <!--- Store thread reference. --->
  • <cfset APPLICATION.Threads[ THREAD.Name ] = Duplicate(
  • THREAD
  • ) />

Calling ColdFusion's Duplicate() here is confusing and weird, but absolutely essential. It has to do with the nature of the THREAD scope. The THREAD scope is a new form of scope that we are not used to dealing with. If you dump out the Java class of the THREAD scope, you will see that it is:

coldfusion.thread.ThreadScope

Now, I don't actually know anything about this scope, but from my experience, most non-setting references to it result in a NULL value. Therefore, in the example above, if we tried to store the THREAD scope directly into the APPLICATION.Threads struct, we would get this in the APPLICATION.Threads struct dump:

undefined struct element

This is because THREAD will store as a NULL value. To demonstrate this without application-level caching, take a look at a VARIABLES-scoped reference to a thread:

 Launch code in new window » Download code as text file »

  • <!--- Run a thread. --->
  • <cfthread
  • action="run"
  • name="ThreadOne">
  •  
  • <!---
  • We don't need to do anything in this thread,
  • we just need to know that it was launched.
  • --->
  • <cfset THREAD.X = true />
  •  
  • </cfthread>
  •  
  •  
  • <!--- Wait for the thread to finish processing. --->
  • <cfthread
  • action="join"
  • name="ThreadOne"
  • />
  •  
  •  
  • <!--- Output the thread data. --->
  • <cfdump
  • var="#VARIABLES.ThreadOne#"
  • />

When we try to run that page, we get the ColdFusion error:

Element THREADONE is undefined in VARIABLES.

The thread, ThreadOne should be available in the VARIABLES scope (since ThreadOne is available without scoping). If we CFDump out the VARIABLES scope, we get a crazy looking user defined function that is called:

_CFFUNCCFTHREAD_CFTEST22ECFM5059090411

What the hell is that? I'll tell you what it is - it's a clear demonstration that the THREAD scope is a very special beast.

So, going back to the first example above, when we duplicate the THREAD scope, we are actually converting the THREAD scope into a standard struct representation of its meta data. This will give us an object with a familiar java type:

coldfusion.runtime.Struct

Doing this, we can now pass around a copy of the thread data that we can actually reference. But this does not mean we have access to the thread itself - just that we have a copy of its meta data.

That being said, let's get back to the demo at hand. It has two parts: the photo download page and then a page that will grab the cached thread data structs and return their status. Here is our modified photo download page:

 Launch code in new window » Download code as text file »

  • <!--- Kill extra output. --->
  • <cfsilent>
  •  
  • <!---
  • Param the FORM variable that will hold our photo urls.
  • Remember, each URL is on its own line (separrated by
  • line returns).
  • --->
  • <cfparam
  • name="FORM.photo_url"
  • type="string"
  • default=""
  • />
  •  
  •  
  • <!--- Trim the form field. --->
  • <cfset FORM.photo_url = FORM.photo_url.Trim() />
  •  
  •  
  • <!---
  • Check to see if the form has been submitted. For
  • this demo, we will know this if there is a value
  • in the form field.
  • --->
  • <cfif Len( FORM.photo_url )>
  •  
  • <!---
  • Loop over the URLs. We can treat the text area
  • as if it were a list of URLs that is using the
  • line break, line return as the list delimiter.
  • --->
  • <cfloop
  • index="strURL"
  • list="#FORM.photo_url#"
  • delimiters="#Chr( 13 )##Chr( 10 )#">
  •  
  • <!---
  • Now that we have our individual URL, let's
  • grab the photo binary using CFHttp and store
  • it directly into a file on the server.
  •  
  • We are going to launch this CFHttp call in a
  • new thread using CFThread. We are not going
  • to wait for this call to finish.
  • --->
  • <cfthread
  • action="run"
  • name="photo_#GetFileFromPath( strURL )#">
  •  
  • <!---
  • Let the current thread save itself to the
  • threads struct in the application. While
  • there is a possible race condition here
  • (since other parts of the code will be
  • reading from this struct), for this demo it
  • is not going to be an issue.
  •  
  • Also, notice that we are not passing the
  • THREAD scope itself. This is crutial as the
  • THREAD scope is a very special scope. By
  • duplicating it we are turning it into a
  • ColdFusion runtime struct (much better for
  • our purposes).
  • --->
  • <cfset APPLICATION.Threads[ THREAD.Name ] = Duplicate(
  • THREAD
  • ) />
  •  
  •  
  • <!--- Save the photo. --->
  • <cfhttp
  • url="#strURL#"
  • method="GET"
  • getasbinary="yes"
  • path="#ExpandPath( './data/' )#"
  • file="#GetFileFromPath( strURL )#"
  • />
  •  
  •  
  • <!---
  • Now that the thread has finished running,
  • have this thread remove itself from the
  • application threads struct.
  • --->
  • <cfset StructDelete(
  • APPLICATION.Threads,
  • THREAD.Name
  • ) />
  •  
  • </cfthread>
  •  
  • </cfloop>
  •  
  • </cfif>
  •  
  • </cfsilent>
  •  
  • <cfoutput>
  •  
  • <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  • <html>
  • <head>
  • <title>ColdFusion 8 - CFThread Demo</title>
  •  
  • <!--- Include the jQuery scripts. --->
  • <script
  • type="text/javascript"
  • src="jquery-latest.pack.js">
  • </script>
  • <script type="text/javascript">
  •  
  • // Load the thread activity via jQuery's
  • // AJAX functionality. This will load the
  • // returned value into the innerHTML.
  • function UpdateThreadActivity(){
  •  
  • $( "##threadactivity" ).load(
  • "./get_threads.cfm",
  • {},
  • function(){
  • setTimeout(
  • UpdateThreadActivity,
  • 250
  • );
  • }
  • );
  •  
  • }
  •  
  •  
  • // When the document has loaded, start
  • // updating the thread activity.
  • $( UpdateThreadActivity );
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h2>
  • Flickr.com Photo Download
  • </h2>
  •  
  •  
  • <!---
  • Check to see if the form as been submitted. For
  • this demo, we will know this if there is a value
  • in the form field.
  • --->
  • <cfif NOT Len( FORM.photo_url )>
  •  
  • <p>
  • Please enter photo URLs that you would like to
  • download. Each URL should be on a single line of
  • the following text area.
  • </p>
  •  
  • <form
  • action="#CGI.script_name#"
  • method="post">
  •  
  • <p>
  • <textarea
  • name="photo_url"
  • cols="70"
  • rows="20"
  • >#FORM.photo_url#</textarea>
  • </p>
  •  
  • <p>
  • <input type="submit" value="Download Now" />
  • </p>
  •  
  • </form>
  •  
  • <cfelse>
  •  
  • <p>
  • Your photos are being downloaded right now:
  • </p>
  •  
  • <!---
  • This part will be updated via jQuery and
  • some nice AJAX requests (see HTML head).
  • --->
  • <p id="threadactivity">
  • Gathering thread activity...
  • </p>
  •  
  • </cfif>
  •  
  • </body>
  • </html>
  •  
  • </cfoutput>

Notice that each CFThread body starts out by caching itself (by name) in the APPLICATION.Threads struct. Then, as it finishes processing, it removes itself (by name) from the same struct. Technically, this is a place where we might be concerned about race conditions, but for this demo, it will not matter. Also notice in place of the "photos are downloading" message, we now have a P tag that is being updated using jQuery and some simple innerHTML-oriented AJAX.

The page that gets called by the AJAX simply iterates over the APPLICATION.Threads meta data structs and outputs the thread data (to be consumed as innerHTML):

 Launch code in new window » Download code as text file »

  • <!--- Kill extra output. --->
  • <cfsilent>
  •  
  • <!---
  • We are going to build up the thread activity HTML.
  • While I normally would return JSON data here, in
  • order to keep the demo as simple as possible (and
  • since AJAX is not the primary goal here), I am just
  • going to render the innerHTML.
  • --->
  • <cfsavecontent variable="strThreadData">
  • <cfoutput>
  •  
  •  
  • <!--- Check to see if there are any threads. --->
  • <cfif StructCount( APPLICATION.Threads )>
  •  
  • <!--- Loop over the active threads. --->
  • <cfloop
  • item="strName"
  • collection="#APPLICATION.Threads#">
  •  
  • <!---
  • Get a short hand reference to the thread.
  • These threads are going to be removing
  • themsleves from the application, so in
  • order to minimize bad data references, get
  • the thread reference.
  •  
  • Once we have an independent reference to
  • the thread, it won't matter if it has been
  • removed from the APPLICATION scope.
  • --->
  • <cfset objThread = APPLICATION.Threads[ strName ] />
  •  
  • <!--- Output the thread data. --->
  • <strong>#objThread.Name#</strong><br />
  •  
  • .....
  •  
  • Start:
  • #TimeFormat(
  • objThread.StartTime,
  • "h:mm TT"
  • )#<br />
  •  
  • .....
  •  
  • Duration:
  • #NumberFormat(
  • ((Now() - objThread.StartTime) * 86400),
  • "0"
  • )#
  • seconds<br />
  •  
  • </cfloop>
  •  
  • <cfelse>
  •  
  • <em>There are no active threads.</em>
  •  
  • </cfif>
  •  
  • </cfoutput>
  • </cfsavecontent>
  •  
  •  
  • <!--- Output the thread innerHTML. --->
  • <cfcontent
  • type="text/html"
  • variable="#ToBinary( ToBase64( strThreadData ) )#"
  • />
  •  
  • </cfsilent>

Again, this is a place where we would have to consider race conditions (since we are iterating over a structure that is being modified by parallel threads), but for this demo, I am not going to worry about it. In order to minimize that chance of bad references, I get a short-hand pointer to the thread meta data struct (rather than referencing it throught the APPLICATION.Threads struct). Therefore, even if the struct does get removed, we will still have a valid pointer to it.

Running the above code to download the following photos:

  • http://farm2.static.flickr.com/1173/527940862_96ba93b86f_o.jpg
  • http://farm2.static.flickr.com/1130/527500500_3c23ffa31f_o.jpg
  • http://farm2.static.flickr.com/1139/525342690_64bfca4370_o.jpg
  • http://farm1.static.flickr.com/102/301096227_7dccd6ab2d_b.jpg
  • http://farm1.static.flickr.com/186/463620314_01395d9ac3_b.jpg
  • http://farm1.static.flickr.com/198/508846122_18632f9acf_o.jpg
  • http://farm1.static.flickr.com/192/482937551_7cbb42872e_b.jpg

... we get the following output:

Your photos are being downloaded right now:

PHOTO_527940862_96BA93B86F_O.JPG
..... Start: 6:30 PM
..... Duration: 1 seconds
PHOTO_508846122_18632F9ACF_O.JPG
..... Start: 6:30 PM
..... Duration: 1 seconds
PHOTO_482937551_7CBB42872E_B.JPG
..... Start: 6:30 PM
..... Duration: 1 seconds

... and then a split second later, after an AJAX update, we get the following update:

Your photos are being downloaded right now:

PHOTO_508846122_18632F9ACF_O.JPG
..... Start: 6:30 PM
..... Duration: 2 seconds

With each successive AJAX call, more of the threads are removing themselves from the APPLICATION.Threads struct. Pretty snazzy, eh? Ok, so we are not technically referencing threads across different page requests, but based on my THREAD-referencing experiments, it seems that this might be the best way to go. Of course, I am just learning here, so it might be that these threads are accessible by name through some other way (but I do not see anything about it in the documentation). At the very least, since we are allowing threads to update their own meta data references, we are tying the meta data copy to the thread across pages.

When it comes to the data in the APPLICATION.Threads struct, remember that it is a duplicate of the thread meta data - it is not actually the thread meta data as it is contained in the running thread. This means that the thread will not update this data as it processes (ie. the Status attribute will never get updated automatically). But, for our purposes, and I assume most purposes, knowing the name and the StartTime will be sufficient.

Download Code Snippet ZIP File

Post Comment  |  Ask Ben  |  Permalink  |  Other Searches  |  Print Page


You Might Also Be Interested In:



Learning ColdFusion 9 - ColdFusion 9 tutorials, samples, examples, demos

Reader Comments

Jun 5, 2007 at 11:01 AM // reply »
6 Comments

So, since the application struct is only copy of the actual threads at the time of their creation I assume you wouldn't be able to view error informtion, or terminate the threads, etc. Is there no way to reference the thread directly after it has been kicked off and the request which created it has finished?


Jun 5, 2007 at 11:03 AM // reply »
204 Comments

One thing to note -if you want to get the thread data, you CAN get it via Evaluate(). Thats how Adobe documents it.


Jun 5, 2007 at 11:52 AM // reply »
25 Comments

Ben,
Thread scopes are not kept in the variable scope and hence you don't see it when you dump the variable scope.
I know you must have figured it out but I am just re-iterating. We don't really recommend sharing the thread scopes across request as this can lead to thread safety of the data.
Rupesh.


Jun 5, 2007 at 2:03 PM // reply »
6,371 Comments

@Brad,

Unless the running thread itself updates that info, yes, it will not be available since the cached struct is only a copy.

@Ray,

Are you talking about using Evaluate() on a different thread?

@Rupesh,

I know that when you refer to a unscoped variable, ColdFusion will start searching for it in an orderly fashion through many different scopes (ex. query output, function local, arguments, page variables, etc.) From that I would gather that threads are stored in some scope that is being searched. Is that the case? Or are threads a totally new beast that is a very special implementation?

As for "Best practices", I agree, cross-page references are going to get very hairy, very fast. I would not recommend trying to do it. In this case, however, since I am only ever referencing a copy of the meta data, I feel that it is not as bad as it might sound. Of course, if the thread crashes and cannot remove itself from the app scope, clearly this can become very corrupt, very fast :)

I think it is definitely playing with fire no matter how you look at it, but I think if done correctly / carefully it could have some interesting potential.


Jun 5, 2007 at 2:05 PM // reply »
6,371 Comments

@Rupesh,

Plus, what is that UDF in the VARIABLES scope that I am seeing? What does it do?


Jun 15, 2007 at 2:28 PM // reply »
9 Comments

The UDF in the variables scope is the actual function that is run in the thread i.e. the code between the cfthread and /cfthread tags are turned in to a UDF and executed in a thread.


Jun 15, 2007 at 4:01 PM // reply »
25 Comments

Thats correct. Threads are stored in a special scope which is actually a request level scope and is searched when you acess any unscoped variable.


Jun 15, 2007 at 6:04 PM // reply »
6,371 Comments

That is good to know. I figure I wouldn't use this stuff like this too often. I am comfortable with a thread setting an application-level variable and then destroying it before it finished executing. Of course, if anything in the thread broke then you would have rogue variables that never get deleted. Clearly, not a fabulous idea, but a cool experiment.


Post Comment  |  Ask Ben

Recent Blog Comments
Jill
Nov 7, 2009 at 11:40 AM
How To Unformat Your Code (Like A Pro)
Derek, I think you might be right - sweet! Thanks for the link :) ... read »
Nov 7, 2009 at 11:25 AM
How To Unformat Your Code (Like A Pro)
I think it would be way easier to just use this http://www.logichammer.com/html-formatter/ He just released v3 and it rocks. ... read »
Jill
Nov 7, 2009 at 7:58 AM
How To Unformat Your Code (Like A Pro)
LMAO - this was pretty funny! I have to admit - I also love to reformat code so I can read it. My boss used to tell me to leave my OCD at home. Now I don't feel so bad after reading everyone else' ... read »
Nov 6, 2009 at 10:10 PM
How To Unformat Your Code (Like A Pro)
The timing of this post is just uncanny. I spent the last 15-20 minutes manually un-formatting my "Ben Nadel" style code within a CFC of mine. I was really digging the readability a few weeks ago, bu ... read »
Roe
Nov 6, 2009 at 5:11 PM
Passing Arrays By Reference In ColdFusion - SWEEET!
ArraySort also reorders the results of these java obj's ... read »
Nov 6, 2009 at 4:53 PM
How To Unformat Your Code (Like A Pro)
I tried to go *back* the other way. Adding formatting is actually a much more complicated problem than removing formatting. Anyway, here is what I could put together with a minimal amount of time: ... read »
Asaf
Nov 6, 2009 at 2:35 PM
ColdFusion GetPageContext() Massive Exploration
Hi, I actually found this post useful. I recently acquired a SSL certificate for my website and when I switched over to HTTPS Internet Explorer would throw an error when trying to download a dynamic ... read »
Nov 6, 2009 at 2:19 PM
How To Unformat Your Code (Like A Pro)
@Chuck, @Nathan, Well, now I feel like it's a challenge.... I accept. ... read »