Click here to Skip to main content
15,881,882 members
Articles / Desktop Programming / WPF
Article

Thread Proxy Mediator Pattern

Rate me:
Please Sign up or sign in to vote.
4.77/5 (11 votes)
22 Jul 2008CPOL9 min read 41.6K   234   56   3
The Thread Proxy Mediator Pattern (TPMP) is a behavioral design pattern that solves many of the problems that plague GUI developers.

The Thread Proxy Mediator Pattern (TPMP) is a behavioral design pattern that solves many of the problems that plague GUI developers. These problems include:

  • Where is the right place to put parsing and validation log?
  • How do you prevent threading issues that cause lockups or repainting glitches?
  • How do you unit test GUI logic?
  • How do you reuse GUI code between different types of user-interfaces?

I’ll explain the pattern with a C# example.

Example User-Interface

Imagine that you have developed an advanced mathematics library. You license this library to users and they in turn integrate it into their own projects. Now, you want to build a user-interface on top of the library and market the result as a separate product. To maintain product separation, the library must be fully decoupled from the user-interface code.

The following code represents the mathematics library:

C#
 1:    public delegate void DivideListener(int percent);
 2:
 3:    public class Divider {
 4:
 5:      private volatile bool running;
 6:
 7:      public void CancelDivision() {
 8:        running = false;
 9:      }
10:
11:      public void Divide(int dividend, int divisor, out int quotient,
             out bool canceled,
12:          DivideListener divideListener) {
13:        running = true;
14:        for (int i = 0; i <= 100 && running; i++) {
15:          Thread.Sleep(100);
16:          if (divideListener != null) {
17:            divideListener(i);
18:          }
19:        }
20:        if (running) {
21:          quotient = dividend / divisor;
22:          canceled = false;
23:        } else {
24:          quotient = 0;
25:          canceled = true;
26:        }
27:      }
28:    }

Divider.Divide() accepts a dividend and a divisor and performs integer division to computer their quotient. The for-loop represents a long computational delay. The progress of the long computation can be monitored via the DivideListener delegate, passed in as an optional parameter. If the divisor is 0, Divide() will throw an exception. Finally, while Divide() is executing, another thread may cancel the operation by calling CancelDivision(). It sets a boolean flag that causes the for-loop to break out early. When that happens, it sets the canceled out parameter to true to indicate that quotient isn’t a valid value.

The Windows Forms user-interface will look like this:

formdivision.jpg

When the user keys in a dividend and a divisor and presses the Divide button, the following progress dialog appears:

formprogress.jpg

The forms above are encapsulated as DivisionForm and ProgressForm respectively. To make this example more interesting, I’ll create WPF versions of these windows later on.

Problems

The two TextBoxes on the DivisionForm accept the dividend and the divisor as strings. Those strings must be parsed into integers to perform the division. If the user enters invalid values, a MessageBox will appear that displays an appropriate error.

Where should the parsing logic go? If I put it inside the button click event handler, which Visual Studio automatically injects into DivisionForm, I won’t be able to reuse it later in the WPF version. Not only that, I can’t unit test event handlers. The only way to debug the logic within event handlers is to run the application.

When the division is performed, it may throw an exception as a result of attempting to divide by zero. Where should I catch that exception and convert it into a error message for the user? Again, if I put it into DivisionForm, I won’t be able to unit test or reuse it in the WPF version.

Another issue is dealing with threading. Since Divider.Divide() takes a very long time to return, the click event handler of the Divide button on the DivisionForm cannot invoke it directly. The application message loop is executed by a single thread. That thread, which I’ll refer to as the “GUI Thread”, services all user-interface events, including repainting controls. If an event handler doesn’t process an event in a timely manner, the user-interface will appear to lockup.

Applying the Pattern

TPMP decouples the user-interface code from the rest of the system as much as possible. Similar to how a traditional web application works, it uses a request-response model. The GUI makes asynchronous requests into rest of the system and responses eventually follow. Though, unlike a web application, the system can push data out to the GUI without a request.

The GUI code is kept very dumb. The parsing and validation logic is moved into separate classes that sit in between the GUI and the rest of the system. This middle-tier known as the “mediator,” acts as a communication bridge and a coordinator between the other tiers. Messages are not allowed to circumvent the mediator.

The first step is to create interfaces that represent the forms:

C#
1:    public interface IDividerForm {
2:      void DisplayQuotient(string quotient);
3:      void ShowError(string errorMessage);
4:    }
5:
6:    public interface IProgressForm {
7:      void DisplayProgress(int percent);
8:      void Display();
9:    }

DivisionForm and ProgressForm implement these interfaces respectively. Later, the WPF windows will use them too. The important point is that the mediator sends information out to the GUI through these interfaces. It is oblivious to the implementation behind them. Also, note that all methods return void and they don’t contain out parameters. The methods of these interfaces represent responses to requests.

The Display() method on line 8 renders the ProgressForm visible. If 100 is passed to DisplayProgress() on line 7, the ProgressForm vanishes.

Next, the mediator is represented by an interface:

C#
1:    public interface IDivisionMediator {
2:      void Divide(string dividend, string divisor,
3:          IDividerForm dividerFormProxy, IProgressForm progressFormProxy);
4:      void CancelDivision();
5:    }

DivisionForm and ProgressForm hold a reference to the mediator as this interface type. Since they are unaware of its implementation, you can switch the mediator with a class that simply logs the values passed into each method. Or, the test mediator can even simulate the rest of the system by responding to requests with predetermined responses. Test mediators make it possible to develop forms outside of system with confidence that they will integrate later without a problem.

The methods of this interface represent requests; they all return void and they don’t accept out parameters. Also, note that the Divide() method on line 2 accepts the dividend and divisor as strings since it’s the mediators job to parse them. Divide() accepts an IDividerForm to enable the mediator to display the quotient or an error message. It also accepts an IProgressForm handle. After successfully parsing the inputs, the mediator renders the IProgressForm visible via its Display() method. Hence, the mediator also contains behavioral logic. It’s the entity that controls and coordinates the forms in addition to parsing and validating inputs.

Here’s the implementation of the mediator:

C#
 1:    public class DivisionMediator : IDivisionMediator {   
 2:      
 3:      private volatile Divider divider = new Divider();   
 4:     
 5:      public void Divide(string dividend, string divisor,    
 6:          IDividerForm dividerFormProxy, IProgressForm progressFormProxy) { 
 7:           
 8:        int a = 0;   
 9:        int b = 0;  
10:        try {  
11:          a = Int32.Parse(dividend);  
12:        } catch {  
13:          dividerFormProxy.ShowError("Dividend is not a valid number.");  
14:          return;  
15:        }  
16:        try {  
17:          b = Int32.Parse(divisor); 
18:        } catch { 
19:          dividerFormProxy.ShowError("Divisor is not a valid number.");  
20:          return;  
21:        }  
22:     
23:        progressFormProxy.Display(); 
24:     
25:        int quotient;  
26:        bool canceled;       
27:        try {  
28:          divider.Divide(a, b, out quotient, out canceled,
             percent => progressFormProxy.DisplayProgress(percent));  
29:        } catch (Exception e) {  
30:          progressFormProxy.DisplayProgress(100);  
31:          dividerFormProxy.DisplayQuotient("?");  
32:          dividerFormProxy.ShowError(e.Message); 
33:          return;  
34:        }  
35:     
36:        if (canceled) {  
37:          progressFormProxy.DisplayProgress(100); 
38:          dividerFormProxy.DisplayQuotient("?");  
39:        } else { 
40:          dividerFormProxy.DisplayQuotient(quotient.ToString());  
41:        }  
42:      } 
43:    
44:      public void CancelDivision() {  
45:        divider.CancelDivision();  
46:      }  
47:    }

Lines 8—21 parse the inputs and display error messages if need be. Line 23 renders the ProgressForm visible if the inputs parsed successfully. Line 28 calls the Divider.Divide() method. Note the use of a lambda expression for the DivideListener parameter. Also, lines 30 and 37 pass in 100 percent into the ProgressDialog to force it to disappear on error or cancellation. If the division was successful, the result is passed back to the DividerForm on line 40.

With these interfaces in place, the parsing and validation logic can be reused and a set of unit testing classes can be developed to simulate the front-end. And, as mentioned, the forms themselves can be developed and tested outside of the system and integrated later. As it turns out, the interfaces also provide the means of solving the threading problems.

The TPMP introduces a thread barrier between the forms and the mediator. The GUI thread is retrained to the forms. It’s not allowed to cross the barrier into the mediator. In fact, the only thread executing within the forms is the GUI thread. On the other side of the wall are worker threads. Worker threads exist within the mediator and the rest of the system, but they can’t cross the barrier into the forms. It achieves this by introducing proxies. Consider this code:

C#
 1:    public class HypotheticalDivisionMediatorProxy : IDivisionMediator {   
 2:      
 3:      private IDivisionMediator target;   
 4:      
 5:      public HypotheticalDivisionMediatorProxy(IDivisionMediator target) {  
 6:        this.target = target;   
 7:      }   
 8:      
 9:      public void Divide(string dividend, string divisor,   
10:          IDividerForm dividerFormProxy, IProgressForm progressFormProxy) { 
11:        DivideClass divideClass = new DivideClass(target);  
12:        divideClass.dividend = dividend;  
13:        divideClass.divisor = divisor;  
14:        divideClass.dividerFormProxy = dividerFormProxy;  
15:        divideClass.progressFormProxy = progressFormProxy;  
16:        ThreadPool.QueueUserWorkItem(new WaitCallback(divideClass.Run));  17:      }  
18:     
19:      public void CancelDivision() {  
20:        CancelDivisionClass cancelDivisionClass = new CancelDivisionClass(target);  
21:        ThreadPool.QueueUserWorkItem(new WaitCallback(cancelDivisionClass.Run)); 
22:      }  
23:    }  
24:     
25:    public class DivideClass { 
26:        
27:      private IDivisionMediator target;  
28:      public string dividend;  
29:      public string divisor;  
30:      public IDividerForm dividerFormProxy;  
31:      public IProgressForm progressFormProxy;  
32:     
33:      public DivideClass(IDivisionMediator target) {  
34:        this.target = target;  
35:      }  
36:        
37:      public void Run(object stateInfo) {  
38:        target.Divide(dividend, divisor, dividerFormProxy, progressFormProxy);  
39:      }  
40:    }  
41:     
42:    public class CancelDivisionClass {  
43:     
44:      private IDivisionMediator target;  
45:    
46:      public CancelDivisionClass(IDivisionMediator target) {  
47:        this.target = target;  
48:      }  
49:     
50:      public void Run(object stateInfo) {  
51:        target.CancelDivision();  
52:      }  
53:    }

Above are 3 classes. HypotheticalDivisionMediatorProxy implements IDivisionMediator. It acts as a middleman between a caller and the target IDivisionMediator passed into the constructor. However, each call is delegated on a pooled worker thread. The DivideClass and the CancelDivisionClass are introduced to encapsulate the arguments passed into the call.

The forms possess an IDivisionMediator handle to this hypothetical proxy that transfers the call from the GUI thread to a worker thread. All the methods of the proxy are asynchronous; they return immediately. The code within the forms is kept very clean. From their point of view, there is no barrier between the forms and the mediator. They make calls as if it they were talking directly to the mediator.

However, coding such proxies by hand is tedious, repetitive and error prone. The trick is to generate them dynamically. The .NET framework, at the time of this writing, does not contain a dynamic proxy. But, it’s possible to generate dynamic classes with the System.Reflection.Emit namespace. In the source code link below, you’ll find a class called ThreadProxyFactory that provides methods for dynamically generating proxies. The method for generating the GUI-thread-to-worker-thread proxy, has this signature:

C#
public static T createProxy<T>(T target) { // ...

It’s used like this:

C#
1:  DivisionMediator divisionMediator = new DivisionMediator();
2:  IDivisionMediator divisionMediatorProxy =
       ThreadProxyFactory.createProxy<IDivisionMediator>(divisionMediator);

For the reverse direction, Control—the base class of Form and all the other Windows Forms controls—provides a method called BeginInvoke() that adds a request to the GUI event queue for servicing a brief time later by the GUI thread. BeginInvoke() returns immediately. To dynamically generate a proxy for the forms interfaces, use this method:

C#
public static T createProxy<T>(T target, Control control) { // ...

The DividerForm makes use of the following code (i.e. this is DividerForm):

C#
1:  ProgressForm progressForm = new ProgressForm();
2:  progressForm.Owner = this;
3:  IProgressForm progressFormProxy = ThreadProxyFactory.createProxy<IProgressForm>(
        progressForm, this);

Here’s the complete definition of DividerForm:

C#
 1:    public partial class DividerForm : Form, IDividerForm {  
 2:      public DividerForm() {  
 3:        InitializeComponent();  
 4:      }   
 5:      
 6:      private void divideButton_Click(object sender, EventArgs e) {   
 7:      
 8:        divideButton.Enabled = false;   
 9:     
10:        ProgressForm progressForm = new ProgressForm(); 
11:        progressForm.Owner = this;  
12:     
13:        DivisionMediator divisionMediator = new DivisionMediator();  
14:     
15:        IDividerForm dividerFormProxy = ThreadProxyFactory.createProxy<IDividerForm>(
               this, this);  
16:        IProgressForm progressFormProxy =
               ThreadProxyFactory.createProxy<IProgressForm>(progressForm, this);  
17:        IDivisionMediator divisionMediatorProxy =
               ThreadProxyFactory.createProxy<IDivisionMediator>(divisionMediator);  
18:        progressForm.DivisionMediatorProxy = divisionMediatorProxy;  
19:     
20:        divisionMediatorProxy.Divide(dividendTextBox.Text, divisorTextBox.Text,
               dividerFormProxy, progressFormProxy);  
21:      }  
22:    
23:      public void DisplayQuotient(string quotient) {  
24:        quotientLabel.Text = quotient;  
25:        divideButton.Enabled = true;  
26:      }  
27:     
28:      public void ShowError(string errorMessage) {
29:        MessageBox.Show(this, errorMessage, "Division Error", MessageBoxButtons.OK,
               MessageBoxIcon.Error); 
30:        divideButton.Enabled = true; 
31:      } 
32:    }

For simplicity, the button click handler creates the mediator and dynamically generates all the proxies before calling Divide(). In a real application, the mediator and many of the proxies would be created on startup. Everything would be wired together, ready to be invoked. Also, note that when the button is clicked, it is disabled to prevent successive clicks making parallel calls into the mediator. It is re-enabled only after a result comes back from the mediator.

Here’s the ProgressForm definition:

C#
 1:    public partial class ProgressForm : Form, IProgressForm {   
 2:     
 3:      private IDivisionMediator divisionMediatorProxy;  
 4:     
 5:      public ProgressForm() {   
 6:        InitializeComponent();   
 7:      }   
 8:      
 9:      public IDivisionMediator DivisionMediatorProxy {  
10:        set {  
11:          divisionMediatorProxy = value;  
12:        }  
13:      }  
14:     
15:      private void cancelButton_Click(object sender, EventArgs e) {  
16:        cancelButton.Enabled = false;  
17:        divisionMediatorProxy.CancelDivision();        
18:      }  
19:     
20:      public void Display() {  
21:        ShowDialog(Owner);  
22:      }  
23:     
24:      public void DisplayProgress(int percent) {  
25:        progressBar.Value = percent;  
26:        if (percent == 100) {  
27:          Dispose();  
28:        }  
29:      }  
30:    }

WPF Version

The source code link below contains a Visual Studio 2008 solution with two projects: a Windows Forms version of the example and a WPF version. The WPF version looks like this:

windowdivision.jpg

windowprogress.jpg

I renamed the interfaces in the WPF version to make the code easier to understand (i.e. I changed “Form” to “Window”), but all the method signatures are exactly the same. Aside from that, the mediator along with its parsing and validation logic is completely reused.

The threading model in WPF is very similar to Windows Forms. WPF controls, including Windows, derive from the abstract class DispatcherObject. DispatcherObject provides a Dispatcher property that returns type Dispatcher. Dispatcher.BeginInvoke() works analogously to Control.BeginInvoke().

Here’s the method of ThreadProxyFactory that you’ll need to create a dynamic proxy for transferring calls from worker threads to the WPF GUI thread:

C#
public static T createProxy<T>(T target, Dispatcher dispatcher) { // ...

Below are the definitions of DividerWindow and ProgressWindow. They are virtually identical to the Windows Forms verions.

 1:    public partial class DividerWindow : Window, IDividerWindow {  
 2:      public DividerWindow() {  
 3:        InitializeComponent();  
 4:      }   
 5:     
 6:      private void DivideButton_Click(object sender, RoutedEventArgs e) {
 7:        divideButton.IsEnabled = false;   
 8:      
 9:        ProgressWindow progressWindow = new ProgressWindow();  
10:        progressWindow.Owner = this;  
11:     
12:        DivisionMediator divisionMediator = new DivisionMediator();  
13:     
14:        IDividerWindow dividerWindowProxy = 
               ThreadProxyFactory.createProxy<IDividerWindow>(this, Dispatcher); 
15:        IProgressWindow progressWindowProxy   
16:            = ThreadProxyFactory.createProxy<IProgressWindow>(progressWindow,
               Dispatcher);  
17:        IDivisionMediator divisionMediatorProxy = 
               ThreadProxyFactory.createProxy<IDivisionMediator>(divisionMediator);  
18:        progressWindow.DivisionMediatorProxy = divisionMediatorProxy;  
19:    
20:        divisionMediatorProxy.Divide(  
21:            dividendTextBox.Text, divisorTextBox.Text, dividerWindowProxy,
                   progressWindowProxy);  
22:      }  
23:     
24:      #region IDividerWindow Members 
25:     
26:      public void DisplayQuotient(string quotient) { 
27:        quotientLabel.Content = quotient;  
28:        divideButton.IsEnabled = true;  
29:      }  
30:     
31:      public void ShowError(string errorMessage) {  
32:        MessageBox.Show(this, errorMessage, "Division Error"); 
33:        divideButton.IsEnabled = true;  
34:      }  
35:     
36:      #endregion  
37:    }  
38:     
39:    public partial class ProgressWindow : Window, IProgressWindow {  
40:     
41:      private IDivisionMediator divisionMediatorProxy;  
42:     
43:      public ProgressWindow() {  
44:        InitializeComponent();  
45:      }  
46:     
47:      public IDivisionMediator DivisionMediatorProxy {  
48:        set {  
49:          divisionMediatorProxy = value;  
50:        }  
51:      }  
52:     
53:      private void cancelButton_Click(object sender, RoutedEventArgs e) { 
54:        cancelButton.IsEnabled = false;  
55:        divisionMediatorProxy.CancelDivision();   
56:      }  
57:     
58:      private void Window_Closed(object sender, EventArgs e) {  
59:        cancelButton.IsEnabled = false;  
60:        divisionMediatorProxy.CancelDivision();   
61:      }  
62:     
63:      #region IProgressWindow Members  
64:     
65:      public void DisplayProgress(int percent) {  
66:        progressBar.Value = percent; 
67:        if (percent == 100) {  
68:          Close();  
69:        }  
70:      }  
71:     
72:      public void Display() { 
73:        ShowDialog();  
74:      }  
75:     
76:      #endregion 
77:    }

References

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Unknown
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralThis is a nice article Pin
John Kenedy S.Kom6-Nov-10 20:46
John Kenedy S.Kom6-Nov-10 20:46 
GeneralMVC (Controller) [modified] Pin
bilo8122-Jul-08 23:11
bilo8122-Jul-08 23:11 
GeneralNice, but... Pin
Andreas Saurwein22-Jul-08 15:36
Andreas Saurwein22-Jul-08 15:36 
Nice article, but really horrible code formatting.


Leon[^] - Enterprise Anti-Spam Server

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.