Inline Asynchronous UI Coding using IDisposable in C# .NET
I recently watched this video on the PowerThreading library: http://blogs.msdn.com/charlie/archive/2008/12/03/jeff-richter-video-on-asynchronous-programming-and-his-power-threading-library.aspx
This is very interesting as Jeff Richter utilizes a strange behavior in the way the C# compiler works with IEnumerables to make a useful and easy-to-use async library. His primary goal was to allow a website\service yield processing during I/O operations, which can provide for better scalability in your app tier.
While this is quite interesting, I personally would avoid using strange compilation patterns to my advantage in an application. Specifically because you have no assurance that the compiler will always do this as the language and subsequent compilers evolve. What I was interested in was the mechanism for including asynchronous calls into code without using a complex series of callbacks.
Now, to be quite fair, Jeff's implementation was an attempt to limit thread spawning, and context switches. I am not going to account for this. I am more interested in parrallel processing.
Let me give you an example. Take for instance a series of objects that the UI is going to take cues from and make subsequent database calls to retrieve extra data for. A simple example is a treeview. If I have a bunch of nodes in a tree and the UI requires that I display the child node or items count for each of these nodes. What you would generally do is loop through the objects and call the DB to retrieve the data, and assign the UI components in one serial block of code. The problem with this is that the database is more than likely capable of servicing the request for numerous nodes simultaneously, but in the UI we are implementing this serially. If the tree structure is complex, and you are showing more than just a node count, it is plausible that each of these requests will take upward of 50ms. If you have several dozen nodes being displayed this can easily add seconds to your response time.
A serial block of code might look like this:
TreeNodeCollection nodes = new TreeNodeCollection();
foreach (TreeNode node in nodes)
{
int objectCount = database.DoSomething((MyObject)node.Tag); //call the database
node.Text = String.Format("{0} ({1})", node.Text, objectCount); //update the UI
}
|
Simple and clean, but slow.
Why not do all of these calls asynchronously, wait for all of the results to filter in, and then update the UI? This should surely be faster than a serial operation. Let's say, we spawn Math.Min(nodes.Count, 10) threads to accomplish this task. So at a maximum, we will use 10 threads to do the work. If we have 20 nodes, it should take at the longest 2 times as long as the longest running call.
The usual issue with this is that it requires you to use several call-back methods, do complicated thread monitoring, and subsequently put the UI into a wait state until all of the external threads have finished. Unfortunately there just is no simple way to do it.
So I got to thinking, what construct can I use to signal an asynchronous block, that would prevent execution beyond it's bounds until all the threads were done. The answer, IDisposable. If I created an IDisposable object and wrapped it in a "using" I could prevent the Dispose method from exiting until all the threads have finished.
Utilizing the rest of my Threading objects, this should be fairly simple to accomplish, all I had to do was add a done flag to my workerbee:
public class AsyncBlock : IDisposable
{
long timeoutMS = 0;
Dispatcher dispatcher = null;
bool started = false;
public AsyncBlock() : this(10) { }
public AsyncBlock(int timeoutSeconds) : this(timeoutSeconds, 5) { }
public AsyncBlock(int timeoutSeconds, int threads) : this(new Dispatcher(threads), timeoutSeconds) { }
public AsyncBlock(Dispatcher dispatcher, int timeoutSeconds)
{
this.dispatcher = dispatcher;
this.timeoutMS = timeoutSeconds * 1000;
this.WorkQueue = new List<WorkerBee>();
}
private IList<WorkerBee> WorkQueue
{
get;
set;
}
public IList<WorkerBee> GetWorkers()
{
return new List<WorkerBee>(this.WorkQueue);
}
public void AddToQueue(WorkerBee bee)
{
this.WorkQueue.Add(bee);
this.dispatcher.AddToQueue(bee);
started = true;
}
public void Start()
{
foreach (WorkerBee bee in this.WorkQueue)
if (!bee.Done)
this.dispatcher.AddToQueue(bee);
started = true;
}
public void Dispose()
{
if (!started && WorkQueue.Count != 0)
{
throw new Exception("Work was never started");
}
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
watch.Start();
bool done = false;
while (!done)
{
done = true;
foreach (WorkerBee bee in WorkQueue)
{
if (!(done = done && bee.Done))
break;
}
if (watch.ElapsedMilliseconds > timeoutMS)
throw new TimeoutException("Timeout elapsed");
if (!done)
Thread.Sleep(1); // wait 1ms to try again
}
watch.Stop();
}
}
public class DatabaseWorkerBee<RefType, ReturnType> : AnyWorkerBee<Delegate>
{
public delegate ReturnType DBDelegate();
private DBDelegate dbDelegate;
public ReturnType ReturnValue
{
get;
set;
}
public RefType ReferenceObject
{
get;
set;
}
public DatabaseWorkerBee(RefType refObject, DBDelegate del)
{
this.ReferenceObject = refObject;
dbDelegate = del;
}
protected override void WorkStart()
{
base.WorkStart();
if (dbDelegate != null)
{
ReturnValue = dbDelegate();
}
}
}
|
Alright, this is now a fairly simple implementation in the UI:
IList<WorkerBee> workers = null;
using (AsyncBlock block = new AsyncBlock(30, Math.Min(nodes.Count, 10))) // do these async with a 30 second timeout
{
foreach (TreeNode o in nodes)
{
TreeNode node = o; //must declare this in the loop, or you get the same object in every worker, this is a documented closure bug in the framework
block.AddToQueue(
new DatabaseWorkerBee<TreeNode, int>( node, delegate { /* Do Database Call Here using the node object*/; }));
}
workers = block.GetWorkers()
}//using will not return until all workers have been processed.
foreach (WorkerBee worker in workers)
{
DatabaseWorkerBee<TreeNode, int> dbworker = worker as DatabaseWorkerBee<TreeNode, int>;
dbWorker.ReferenceObject.Text = String.Format("{0} ({1})", dbWorker.ReferenceObject.Text, dbWorker.ReturnValue); //update the UI
}
|
Woot! All of the nodes are executed async, and all of the values are returned properly. One thing to note, notice I am declaring a redundant TreeNode object inside the loop. This is necessary, since we are using an anonymous method inside of the loop. If we were to just reference the "o" in the iterator, we would get the same object passed to each anonymous method.
My Threading Objects can be found here: ThreadLib.zip
[Edit]
After further review and testing this code works quite well, except in the instance of windows authenticated database connections. Because we are spawning new threads, and your security context is thread based, all of the subsequent database calls were made using the applications default user. This isn't a problem in Windows UI's but in Web apps this is not good. It could be as simple as setting the new threads to the same security context as the spawning thread, but I'm not sure I want that to happen for every thread. I will work this out and do an update.
I have worked this security context issue out. If anyone is interested in it, email me.