Introduction
This article demonstrates a small, generic and reusable infrastructure for running tasks asynchronously and allowing them to update their progress dialogs in a simple way. This solution is for Silverlight but can be used with Winforms or WPF with a very small change.
Background
When doing UI related programming, very often we need to perform background processing that we usually run on worker threads as we don't want to block the UI thread and make our UI nonresponsive for the period of the operation. Probably worker threads or the thread pool is used to perform the task in the background but in many cases we want to show some progress about the task while running.
Problem
As it is well known, we must not call methods or access properties of a UI element from a worker thread, just from the thread that created these elements. This thread is usually called the UI thread. (However an application can have multiple UI threads.) This is the thread that runs the message pump and spends most of its time waiting for different messages and dispatching them to the appropriate UI elements’ handlers. So when user is clicking in the application, messages are generated and posted to the message queue. The UI thread gets these messages and invokes the handlers. This way these handlers are always called from the UI thread. (From .NET 2.0, if we access a value on a control from a worker thread we get an InvalidOperationException: Cross-thread operation not valid: Control 'TextBox1' accessed from a thread other than the thread it was created on.)
So we need to get these UI updates called from the UI thread. (Marshal to UI thread) In WinForms, we can use any control’s BeginInvoke
or Invoke
method (coming from the ISynchronizableInvoke
interface). Simply it takes an arbitrary delegate and an object array for the parameters. That delegate will be somehow wrapped and posted in a message to the message pump and so the UI thread will eventually call it.
Control.BeginInvoke(Delegate del, object[] args);
In Silverlight, we do the same thing using:
Control.Dispatcher.BeginInvoke(Delegate del, object[] args);
I presume (and expect) many of you are familiar with these techniques, but still the problem is that these require additional coding to define delegates, and make these calls properly, even if you make these from the worker thread or a worker thread event, you need to call the BeginInvoke
passing a delegate and the arguments. This will also mix with the original logic and scatter all over in the code that makes code less readable, uglier.
Solution
The following solution is made for Silverlight, but very similarly can be ported to .NET/Winforms. The main aim was to somehow hide these message marshallings in the progress dialog box itself. So from the worker threads, we can call directly our UI update methods on the progress dialog as it will in itself marshal the calls invoking the proper event handlers from UI thread.
1. Progress Dialogs
DefaultProgressDlg
First, I created a base dialog class called DefaultProgressDlg
deriving from ChildWindow
. This will be the ultimate base class providing all the common functionalities for all our descendant progress dialogs. It has a protected PostMessage(Delegate, object[] args)
method that takes an arbitrary delegate with parameters and marshal it to the UI thread. (In case it is called from the UI thread it invokes the delegate directly without marshalling.) It also defines two public
common operations that all dialog box will have: SetTitle(string)
and CloseWindow()
. These methods just set up a delegate for their corresponding handler methods and pass it to PostMessage
with arguments. So, all in all these defined public
operations (and only these) will be safe to call from worker threads. This DefaultProgressDlg
has in fact nothing else displayed on it.
Creating Custom Progress Dialogs
If we want (and probably we will) to show additional information, we just derive from this DefaultProgressDlg
and place our controls to the dialog (design the XAML). Then we define our public
operations that call the inherited PostMessage
. We also need to define the private
message handlers as well that are passed to the delegates so they will be always called from the UI thread and can make the UI update operations.
The public
operation (Post the delegate):
public void SetTitle (string text)
{
Action<string> a = new Action<string>(OnSetTitle);
PostMessage(a, text);
}
The corresponding private
message handler (called on the UI thread):
private void OnSetTitle (string text)
{
this.txTitle.Text = text;
}
If we use from zero or maximum four parameter methods, the easiest is to use Action()
or Action<t>(T t)
delegate overloads defined in the .NET base class library, but here we can use any kind of delegate as PostMessage
accepts a Delegate
type that is base for all delegates.
2. Tasks
The AsyncProcess
assembly also defines an abstract TaskBase<t>
class to write your tasks. All you need when writing your custom task is to derive from it, set the appropriate ProgressDialog
class type as the T
Generic parameter, and override the Run abstract
method with your custom logic. The TaskBase
class has a Start()
method and when that gets called it creates the specified Progress Dialog, displays it, and launches the Run
method asynchronously on a worker thread, and as the progress dialog instance is a protected
property inside TaskBase
, your custom task will always access it, and can directly call the defined public
operations on the progress dialog.
** Note: Only the defined custom operations are safe to call from non UI thread! ***
Creating a custom task:
public class TestTask1 : TaskBase<mycustomdialog>
{
protected override void Run()
{
ProgressDialog.SetTitle("Test1 running..");
ProgressDialog.SetTitle("Test1 finished..");
}
protected override Dispose(bool disposing)
{
base.Dispose(disposing);
{
}
Note: You don’t need to call ProgressDialog.CloseWindow()
at the end, as the base class will call it for you. It will also invoke completion callback if specified (see later), and call dispose on the task.
AsyncController – Starting the Task
The AsyncController
is a simple static
class having few static
methods where you can start your tasks. The TaskBase Start()
method is in fact set to internal so it can only be started using the AsyncController
. 3 overloads defined:
Starts the task and returns immediately. No callback assigned.
public static void Queue(TaskBase task)
Starts the task and assigns a callback that is called when task finished. Returns immediately.
public static void Queue(TaskBase task, Action<taskbase> completionCallback)
Starts the task, assigns a callback, if wait is true the calling thread is blocked until task finished.
public static void Queue(TaskBase task, Action<taskbase> completionCallback, bool wait)
The following overloads get delegates that will be called on running. This is handy if you don't have custom task just want to use a method as the task. The delegate is of type Action<TP>
(where TP
is the Dialog being used). These overloads build an instance of the internal shipped DelegateTask
.
public static void Queue<TP>(Action<TP> taskRun)
public static void Queue<TP>(Action<TP> taskRun, bool IsCancellable)
Also it is very important that the you can start tasks from any thread!
Dialog boxes are always created on the UI thread, the TaskBase class makes sure of it! .
Summary
As you can see this is really a compact solution for the outlined problem, you just need to define your custom progress dialog (writing public
operations and private
handlers), and the custom tasks (overriding Run()
method) and that’s it. The dialog creation, launching worker thread, marshalling the delegates, and some other functionality are encapsulated in the base classes.
Additional Features
Cancelling Operation
There is also a built in way to allow the operations to be cancelled. Both the Dialog being used and the actual Task should support it.
To make your task cancellable you should override the IsCancellable
property and return true
. In this case when the user cancels the operation on the Dialog box, your task is notified in the OnCancelled()
method. So you should also override this and put your cancellation logic here.
public class TestTask1 : TaskBase
{
private bool _stop = false;
protected override bool IsCancellable
{
get {return true;}
}
protected override void Run()
{
ProgressDialog.SetTitle("Test1 running..");
while(!_stop)
{
}
ProgressDialog.SetTitle("Test1 finished..");
}
protected override OnCancelled()
{
ProgressDialog.SetTitle("Cancelling..");
_stop = true;
{
}
To make a custom Dialog box to support cancellation, all you have to do is to override the CancelButton
property and return an instance of a System.Windows.Controls.Button
type. Just place a Button
anywhere in the XAML and return this button in the CancelButton
property.
Note: If a task’s IsCancellable
is set to false
even the Dialog box supports it, the cancel button will be set to invisible. The other way, even if the task supports cancellation, but no CancelButton
is specified in Dialog Box being used, the user will never be able to cancel the operation.
Wait for Operation to Complete
As you could see, when you launch the task in AsyncController
you can set wait flag to true, which will cause the calling thread to wait (block) until the operation has completed.
** Note: You should never use it when on UI thread as it would block it. It can only be used when launching task from a worker thread. (For example a running task can launch a child task and wait until that finished.) **
Task Completion Callback
You can also specify a completion callback when launching a task in AsyncController
. This way you can be notified when background task finished.
** Note: Completion callback is called from the worker thread! **
3 Shipped Progress Dialogs
Apart from the DefaultProgressDlg
which can display a title, 3 other pre-built Dialog boxes are shipped in the AsyncOperation.dll.
DialogOnlyStatus
It can display a main status message on the top of the window client area
DialogOneProgress
The same as DialogOnlyStatus
but can also display a progress bar with a status text for the progress bar.
DialogTwoProgress
The same as DialogOnlyStatus
but can also display two progress bars with two status texts for each the progress bars.
Question
Here, I also have a question.
As you could see, theoretically the DialogOneProgress
should derive from DialogOnlyStatus
as it has the same main status text plus the progress bar and progress status.
The DialogTwoProgress
could be derived from the DialogOneProgress
as it has one additional progress bar. Currently as seen on the class diagram, all 3 Dialogs derive from the basic DefaultProgressDlg
, so some functionality is repeated.
The problem is that I could not find a way to inherit a control or window from one another that also has XAML. Should you know a way, please let me know! Thanks.
Attached Source Code
In the source code, you can find the project for AsyncOperation.dll, and also a test Silverlight application that defines some custom tasks that use the pre-built dialogs.