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 »
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 »
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 »
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 »
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:
... 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
Comments (8) | Post Comment | Ask Ben | Permalink | Other Searches | Print Page
Lenny And Bo, ColdFusion Programmers (Vol. 16)
Learning ColdFusion 8: Ping - User Defined Function (Inspired By Ray Camden)
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?
Posted by Brad Wood on Jun 5, 2007 at 11:01 AM
One thing to note -if you want to get the thread data, you CAN get it via Evaluate(). Thats how Adobe documents it.
Posted by Raymond Camden on Jun 5, 2007 at 11:03 AM
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.
Posted by Rupesh Kumar on Jun 5, 2007 at 11:52 AM
@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.
Posted by Ben Nadel on Jun 5, 2007 at 2:03 PM
@Rupesh,
Plus, what is that UDF in the VARIABLES scope that I am seeing? What does it do?
Posted by Ben Nadel on Jun 5, 2007 at 2:05 PM
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.
Posted by Tom Jordahl on Jun 15, 2007 at 2:28 PM
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.
Posted by Rupesh Kumar on Jun 15, 2007 at 4:01 PM
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.
Posted by Ben Nadel on Jun 15, 2007 at 6:04 PM