Introduction
Let's face it, writing code that executes in the background is difficult. You can either write threaded code and marshal objects between your threads, or you can write state machines which are really unreadable. It would be nice if you could just write a method and somehow invoke it to run in the background without worrying about marshaling objects. It turns out that with a bit of trickery you can pretty much do just this...
Using the code
What we would like to do, is write a method like the following and have it run without blocking our UI.
private long CalculateFibonacci(long n)
{
long a = 0;
long b = 1;
for (long i = 0; i < n; i++)
{
long temp = a;
a = b;
b = temp + b;
}
return a;
}
To do this we need to define points in the code where it will cooperatively give up control and allow other tasks to execute. This will allow the UI to update, etc. What we need is a "yield return
" statement.
As many of the readers of this article will know, C# has just such a statement. The drawback to the statement is that it can only be used when defining an enumerator. So what is the difference between our method and an enumerator? There doesn't need to be all that much of a difference. If we imagine that our method is executing iterative cycles to generate the result we are looking for, then we could insert
yield
s at the end of each iteration to return control from the method, but without losing the state of the method.
So what if our code above looked like this:
private IEnumerable<double> CalculateFibonacci(long n, Action<long> setResult)
{
long a = 0;
long b = 1;
for (long i = 0; i < n; i++)
{
long temp = a;
a = b;
b = temp + b;
yield return (double)i / (n - 1);
}
setResult(a);
}
It's still pretty readable, right? No complicated state machines or thread marshaling, right?
The only differences are that we are now returning the progress of the method from the
yield return
and the actual return requires a delegate to communicate its value to the caller.
Now we have the basics of cooperative multitasking, but it would probably be nice to have a central place that calls and manages this logic.
For that I created the
WorkPool
class:
public class WorkPool
{
private ObservableCollection<worktask> workers = new ObservableCollection<worktask>();
private int index;
public IEnumerable<worktask> Workers
{
get
{
return this.workers;
}
}
public void Add(string name, IEnumerator<double> worker)
{
this.workers.Add(new WorkTask(){ Name = name, Enumerator = worker, Progress = 0.0});
}
public bool Run(TimeSpan timeout)
{
DateTime runTill = DateTime.Now + timeout;
while (DateTime.Now < runTill)
{
if (workers.Count == 0)
{
break;
}
else if (this.index >= workers.Count)
{
this.index = 0;
}
IEnumerator<double> next = workers[this.index].Enumerator;
var hasMore = next.MoveNext();
workers[this.index].Progress = workers[this.index].Enumerator.Current;
if (!hasMore)
{
workers.RemoveAt(this.index);
}
else
{
this.index++;
}
}
return workers.Count > 0;
}
}
The WorkPool
class has an Add
method for adding new tasks and a
Run
method that when called will execute any queued task and quit after the timeout value is reached. Pretty neat, right?
So let's wrap this up into an application. Below is a snippet from my WPF test project.
private void Button_Click(object sender, RoutedEventArgs e)
{
var value = long.Parse(this.num.Text);
var i = CalculateFibonacci(value, this.ShowResult);
this.pool.Add(string.Format("Fibonacci {0}", value), i.GetEnumerator());
this.Dispatcher.Invoke(new Action(this.ApplicationIdle), DispatcherPriority.ApplicationIdle, null);
}
private void ApplicationIdle()
{
if (this.pool.Run(new TimeSpan(0, 0, 0, 0, 50)))
{
this.Dispatcher.Invoke(new Action(this.ApplicationIdle),
DispatcherPriority.ApplicationIdle, null);
}
}
Here I have a button click event that creates our task, queues it in the work pool, and then invokes a delegate that runs in application idle to execute the task. The idle method executes the pool for 50 milliseconds, then if it needs to execute some more, it requeues itself to run in application idle again.
Conclusion
The attached code shows a WPF application that calculates Fibonacci numbers in idle time on the UI thread using the method described above. The code is, I believe, very readable, maintainable, and robust. Obviously on a multi-core machine, you can achieve more by moving away from a single threaded design. I have however seen very complicated applications running in single threads and this is a neat way of keeping your code manageable.
History
- 8/22/2012 - Initial submission.