Introduction
Communicating across a network can be time consuming manifesting itself as an unresponsive user interface. Avoiding long and non-deterministic operations on the UI thread will not increase overall performance but it will give the user some degree of confidence that the application hasn’t silently died.
One of my earlier solutions (see my earlier article: Threading and the UI) called for encapsulating the long running operation within a Task
class. The Task
would in turn fire an event to notify the caller of its completion. Unfortunately, this implementation had very tight coupling between the requested operation and the mechanics of the task used to prepare, invoke and return information from the spawned thread.
It soon became clear that a more robust solution was needed, which would decouple the threading control of the Task
class and whatever operation it was supposed to control.
A more generic Task Pattern Design
The decoupling I was looking for was achieved by abstracting away the “requested operation” details and encapsulating them into an object that could be operated upon without having to know any of the request details. Sounds a lot like the Command Pattern and that is exactly what the solution is modeled after.
Command Pattern in Review
The basic intent of the Command Pattern is to encapsulate a request inside an object. The Command Pattern allows you to decouple the requester of an action from the object that actually performs the action. The action and the receiver may be bound together within the command object which in turn is exposed through the Execute
method of the Command
interface. Another point worth noting is that the invoker of the command is also unaware of how the work is done. The invoker (our Task
object) simply calls Execute
on the Command
object at some future point in time without needing to know what type of processing is to be done.
The pattern provides quite a bit of flexibility in regards to how much or how little logic you design into the concrete command. From the implementation point of view you can have two types of concrete command classes. Forwarding Commands are those commands that simply call a member function within a Receiver object. Concrete commands may also embed logic, which goes well beyond simple delegation. These are referred to as Active Commands. Active Commands can go so far as to service the entire request without the need for an embedded receiver. My rendition of the Task pattern is a blend of the two concepts. The concept of a reader object must be known by the concrete command. The reader will in turn abstract away derived class details in an attempt to maintain a loose coupling.
The Task-Command Pattern
The Task pattern encapsulates a command object. It provides an interface by which to load the command object, subscribe to command completion events and a means by which to invoke the processing of the command asynchronously.
public class Task
{
private Thread _thrd;
private Command _cmd;
public delegate void TaskPassed();
public delegate void TaskFailed();
public event TaskPassed passed;
public event TaskFailed failed;
public void setCommand(Command cmd)
public void RunAsynchTask()
} |
|
Concrete Command Class
In the sample code, we bind together a SQL statement with a database reader object. The concrete command (SelectCommand
) Execute
method simulates a somewhat lengthy database fetch.
The fetch instructs the reader to perform the requested SELECT
command expecting back an ArrayList
.
public class Command
{
public Command(){}
public virtual void execute() {}
public virtual bool passed() {return false;}
}
public class SelectCommand : Command
{
private DataAccess.DBReader _reader;
private bool _passed;
private string _sp;
private ArrayList _al;
public SelectCommand(DataAccess.DBReader rdr, string sp)
{
_reader = rdr;
_sp = sp;
}
public override void execute()
{
Thread.Sleep(5000);
_al = _reader.select(_sp);
_passed = _reader.selectPassed();
}
The SelectCommand
object stores the resultant ArrayList
, which can be queried by the Task initiator through a public member function.
Wiring things up
To use the Task
class we first instantiate the object on the heap and wire up to the event notifications. The Task
provides an event signaling that processing was successful, and an event which denotes the occurrence of a failure.
Task task = new Task();
task.passed +=
new WinFormTaskPattern.Task.TaskPassed(this.ListBoxSelectPassed);
DataAccess.DBReader reader = new DataAccess.GroupDB();
cmd = new SelectCommand(reader, "A Select Stored Proc");
task.setCommand(cmd);
beginListBoxInitialization(this.lstBox, this.btnRefresh);
task.RunAsynchTask();
Once the Task
object has been wired-up, we then tell it what command it will process. During the setup of the command, we associate an applicable reader object. At this juncture all that remains is to tell the Task to process.
Summary
In this article I incorporated the Command Design Pattern in order to provide a higher degree of reusability. The delegate mechanism of the CLR simplifies greatly the work required in separating UI thread operations from long running processes. Using these mechanisms through higher-level design patterns such as the one I described should seriously be considered in any GUI that must communicate across the wire. That of course is just my opinion.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.