Running Asynchronous Tasks from WPF GUI Event Handlers

A short digression from multimethods which the next article will continue to focus on.  This article examines different ways for event handlers to run lengthy tasks asynchronously which may be updating GUI controls while running.

There are quite a few ways to call tasks asycnrhonously in .NET 3.x:
1) Do it in GUI Thread – ok, this is a cop out
2) Create thread explicitly
3) Create thread implicitly using thread pool
4) Create thread implicitly using delegate BeginInvoke
5) Use Background worker

To download source, go here

We create a task runner interface to facilitate for different implementations:

public interface ITaskRunner
{
    ContentControl DisplayArea { get; set; }  // DisplayArea.Content will be set while task is running.
    Action DoneCB { get; set; } // Called when task is done
    string Name { get; set; }  // Name of task runner
    int LoopCount { get; set; } // Loop count

    void Run();
}

The GUI looks like this:

Combo box offers a choice of the task runner implementation.  Pressing start will start task runner and button becomes disabled until task is finished.

The start button handler:

private void btnStart_Click(object sender, RoutedEventArgs e)
{
    ITaskRunner runner = GetTaskRunner();
    runner.DisplayArea = lblOutput;
    runner.LoopCount = 3;   // 3 seconds
    runner.DoneCB = EnableButton;
    btnStart.IsEnabled = false;
    runner.Run();
}

The EnableButton function may be called outside of GUI thread, so it uses Dispatcher to invoke enabling of button:

private void EnableButton()
{
    Dispatcher.Invoke(new Action(() => { btnStart.IsEnabled = true; }));
}

To promote some code sharing, we create an abstract class DefaultTaskRunner which has common code needed by all implementations, such as definitions of the properties:

public abstract class DefaultTaskRunner : ITaskRunner
{
    protected void CallDone() { … }

    public System.Windows.Controls.ContentControl DisplayArea { get; set; }
    public int LoopCount { get; set; }
    public string Name { get; set; }
    public Action DoneCB { get; set; }
    public abstract void Run();

 

DefaultTaskRunner also has a static function to return a list of task runners.  Instead of hardcoding, we will use reflection to discover all concrete classes which implement the ITaskRunner interface:

    public static IEnumerable<ITaskRunner> GetRunners()
    {
        var q = from t
                in Assembly.GetAssembly(typeof(DefaultTaskRunner)).GetTypes()
                where typeof(ITaskRunner).IsAssignableFrom(t) && !t.IsAbstract
                select Activator.CreateInstance(t) as ITaskRunner;

        return q;
    }

Let’s look at some of the implementations.  For example, the Delegate approach:

public override void Run()
{
    Action act = RealRun;
    act.BeginInvoke(null, null);
}

private void RealRun()
{
    Dispatcher dispatch = DisplayArea.Dispatcher;

    dispatch.Invoke(new Action(
        () => { DisplayArea.Content = "Running"; }));

    for (int i = 0; i < LoopCount; ++i) { … }

    dispatch.Invoke(new Action(
        () => { DisplayArea.Content = "Done"; }));

    CallDone();
}

ThreadPool approach is very similiar:

public override void Run()
{
    ThreadPool.QueueUserWorkItem(new WaitCallback(RealRun));
}

Backgroundworker approach is the most complicated:

public override void Run()
{
    m_bg.RunWorkerAsync();
}

private void m_bg_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    CallDone();
}

private void m_bg_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    if (e.ProgressPercentage == 0)
    {
        DisplayArea.Content = "Running";
    }
    else if (e.ProgressPercentage == 100)
    {
        DisplayArea.Content = "Done";
    }
}

private void m_bg_DoWork(object sender, DoWorkEventArgs e)
{
    m_bg.ReportProgress(0);

    for (int i = 0; i < LoopCount; ++i)
    {
        Thread.Sleep(1000);
    }

    m_bg.ReportProgress(100);
}

I like delegate approach the best.  It requires the least amount of code, and the code is also the simplest.

Technorati Tags: ,,

This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s