Yesterday I wrote a blog entry where I answered various questions about threading. One of the topics I touched on was how you could monitor the status of threads that you aren't waiting around for. I promised a small demo - and at lunch today I whipped one up. It isn't the most elegant example, but hopefully it will help demonstrate the concept. I'll discuss bits of the code, and at the end of the entry will present the entire template for those who want to cut/paste.
My template begins with a simple 'hook' to let me nuke my data and reset my status:
<cfif isDefined("url.id")>
<cfset structClear(application)>
</cfif>
Next I set up how many tasks I'll be running, and create some variables to store my information:
<cfset totalTasks = 4>
<cfparam name="application.tasksRunning" default="#structNew()#">
<cfparam name="application.tasksDone" default="0">
The tasksRunning structure is meant to store what threads are running. This way I don't rerun the same thread. The tasksDone value simply records how many tasks are done.
Now I'm going to loop and create 4 tasks (4 being the value of totalTasks):
<cfloop index="x" from="1" to="#totalTasks#">
<!--- name the task --->
<cfset taskName = "Task #x#">
I set a name for the task based on the number. Next I'm going to see if I should bother running the task:
<cfset needRun = false>
<!--- determine if we need to run --->
<cflock scope="application" type="readOnly" timeout="30">
<cfif not structKeyExists(application.tasksRunning, taskName)>
<cfset needRun=true>
</cfif>
</cflocK>
I lock the access since it is possible this template may be reloaded multiple times (you will see why later). Now for the actual task:
<cfif needRun>
<cfoutput><p>I'm starting #taskName#</p></cfoutput>
<cflock scope="application" type="exclusive" timeout="30">
<!--- it's possible someone else started --->
<cfif not structKeyExists(application.tasksRunning, taskName)>
<cfthread name="#taskName#">
<!--- sleep 5-15 seconds --->
<cfset sleep(randRange(5,15) * 1000)>
<cflock scope="application" type="exclusive" timeout="30">
<cfset application.tasksDone++>
</cflock>
</cfthread>
<cfset application.tasksRunning[taskName] = 1>
</cfif>
</cflock>
</cfif>
Ok a lot here. The first thing I do is output a simple message to screen. This was purely for my debugging. I lock the application scope again and check again. Why? I had checked to see if I needed to run the code in a read lock. It is possible that the value will be true because an earlier request hasn't completed yet. So I check again in the exclusive lock to be extra sure I need to run.
Next I create my thread. Inside my thread I'll sleep for a random amount of seconds. When the sleep is done, I increment my counter of tasksDone.
Lastly, I set a marker for the task so I know it is running. You will note that I don't remove this marker ever. I didn't want reloads of the script to keep restarting the tasks. The idea is that these tasks run once - period. (Unless I pass url.id, and then we start from nothing.)
Outside of the loop I display my status:
<cfoutput>
<p>
There are #application.tasksDone# out of #totalTasks#.
</p>
</cfoutput>
And then I offer some simple links to make testing easier:
<cfif application.tasksDone is totalTasks>
<p>
<a href="testi.cfm?id=1">Start over.</a>
</p>
<cfelse>
<p>
<a href="testi.cfm">Reload.</a>
</p>
</cfif>
If you run this template, you can reload again and again and watch while the tasks done slowly increase. If you wanted to get Paris-Hiltony, you can do the same checks via Ajax and a progress meter. (I'd promise a demo, but with MAX next week my free time is a bit too tight.)
Here is the complete script:
<cfif isDefined("url.id")>
<cfset structClear(application)>
</cfif>
<cfset totalTasks = 4>
<cfparam name="application.tasksRunning" default="#structNew()#">
<cfparam name="application.tasksDone" default="0">
<cfloop index="x" from="1" to="#totalTasks#">
<!--- name the task --->
<cfset taskName = "Task #x#">
<cfset needRun = false>
<!--- determine if we need to run --->
<cflock scope="application" type="readOnly" timeout="30">
<cfif not structKeyExists(application.tasksRunning, taskName)>
<cfset needRun=true>
</cfif>
</cflocK>
<cfif needRun>
<cfoutput><p>I'm starting #taskName#</p></cfoutput>
<cflock scope="application" type="exclusive" timeout="30">
<!--- it's possible someone else started --->
<cfif not structKeyExists(application.tasksRunning, taskName)>
<cfthread name="#taskName#">
<!--- sleep 5-15 seconds --->
<cfset sleep(randRange(5,15) * 1000)>
<cflock scope="application" type="exclusive" timeout="30">
<cfset application.tasksDone++>
</cflock>
</cfthread>
<cfset application.tasksRunning[taskName] = 1>
</cfif>
</cflock>
</cfif>
</cfloop>
<cfoutput>
<p>
There are #application.tasksDone# out of #totalTasks#.
</p>
</cfoutput>
<cfif application.tasksDone is totalTasks>
<p>
<a href="testi.cfm?id=1">Start over.</a>
</p>
<cfelse>
<p>
<a href="testi.cfm">Reload.</a>
</p>
</cfif>