Using Retlang for multi-threaded windows form code
I've been a fan of Retlang for a long time and have been meaning to write up some of the ways I've used it. This is the first of 3 blog entries and in this one I'll describe a way of using Retlang to avoid the dreaded InvalidOperationException "Control accessed from a thread other than the thread it was created on".
It avoids lots of nasty boiler plate calls around InvokeRequired(), this isn't new and has been described before but what I want to show how it can also make testing easier and work with existing MVC patterns.
We are going to use Retlang to allow controller code and view code to communicate over message channels in a thread safe way.
Lets imagine a very simple implementation of MVC to illustrate the approach.
Here's the test, we are just adding numbers together and then displaying the answer in the view.
[TestFixture]
public class TestSimpleController
{
private Model model;
private SimpleView view;
private MockRepository repository;
[SetUp]
public void SetUp()
{
repository = new MockRepository();
model = repository.StrictMock<Model>();
view = repository.StrictMock<SimpleView>();
}
[Test]
public void testShouldDoSimpleAdd()
{
model.PushNumber(10);
model.PushNumber(5);
model.SumStack();
LastCall.Return(15);
view.DisplayCurrentTotal(15);
repository.ReplayAll();
var simpleController = new SimpleController(model,view);
simpleController.SendNumber(10);
simpleController.SendNumber(5);
simpleController.Sum();
repository.VerifyAll();
}
}
I'm using RhinoMock for mocking the view and the model. The controller implementation looks like
public class SimpleController
{
private readonly Model model;
private readonly SimpleView view;
public SimpleController(Model model, SimpleView view)
{
this.model = model;
this.view = view;
}
public void SendNumber(int i)
{
model.PushNumber(i);
}
public void Sum()
{
var result = model.SumStack();
view.DisplayCurrentTotal(result);
}
}
Now suppose we have to extend the implementation to deal with a long running task, we don't want to block the view thread as that is bad for user experience so we create a new thread to call the model from. Here is the controller code for that:
public void DoPrediction()
{
var thread = new Thread(InvokeModelWork);
thread.Start();
}
private void InvokeModelWork()
{
var result = model.LongRunningCalculation();
view.DisplayCurrentTotal(result);
}
It's worth noting here that a test for this code written in the same way as for our SimpleAdd will quite likely start failing at this point as asserts will be made on the calling thread before the "worker" thread has called the model and the view. Here is that naive and incorrect version of the test
[Test]
public void testShouldInvokeLongRunningCalc()
{
model.PushNumber(33);
model.PushNumber(44);
model.PushNumber(11);
model.LongRunningCalculation();
LastCall.Return(88);
view.DisplayCurrentTotal(88);
repository.ReplayAll();
var simpleController = new SimpleController(model, view);
simpleController.SendNumber(33);
simpleController.SendNumber(44);
simpleController.SendNumber(11);
simpleController.DoPrediction();
repository.VerifyAll();
}
This sort of test is a common problem in many code bases where the threading is not added until after complaints from the users about performance problems - this can create a lot of problems with the testing and is a very common source of bugs in MVC code. It is easy to tie yourself in knots using the mock framework to signal threads that a method was called or, even worse, using Thread.Sleep() to pause inside of the test. My experience has been that both of these are a source of "mysterious" test/build failures and are very hard to maintain.
Anyway even if we make our test pass we'll see the following when we try to run the code for real in a Windows Forms implementation of our view interface:
{"Cross-thread operation not valid: Control 'textBoxResult' accessed from a thread other than the thread it was created on."}
For completeness here is the very simple user control that implements the view.
public partial class SimpleControl : UserControl, SimpleView
{
private readonly SimpleController controller;
public SimpleControl()
{
controller = new SimpleController(new DoesMath(),this);
InitializeComponent();
}
public void DisplayCurrentTotal(int i)
{
textBoxResult.Text = i.ToString();
}
private void buttonSum_Click(object sender, EventArgs e)
{
controller.Sum();
}
private void buttonPredict_Click(object sender, EventArgs e)
{
controller.DoPrediction();
}
private void buttonSubmit_Click(object sender, EventArgs e)
{
var input = int.Parse(textBoxInput.Text);
controller.SendNumber(input);
}
}
So how can Retlang help?
Retlang lets us create channels which different threads can use to communicate. We can create one of these channels from the dispatch thread of a windows form class.
Here is the new version of the constructor for the controller:
private Channel<int> viewChannel;
public SimpleController(Model model, SimpleView view)
{
this.model = model;
viewChannel = new Channel<int>();
viewChannel.Subscribe(view.Fibre, view.DisplayCurrentTotal);
}
In this simple case we have one channel over which we will send int's and we also have have just one subscriber which is the view.DisplayCurrentTotal() method. We also ask the view to provide use with the Fiber to use, in this case this allows the view to provide a fiber that it knows will be safe to execute the subscriber method(s) on.
In more advanced cases you might want to have many channels or multi-plex different message types over the same channel and use the Dispatcher pattern to route those to the right subscribers on the view. I've done just that in a real example and used reflection to create mappings from each view method to the the correct message type being received from the channel.
On with our simple example; so what do Sum() and DoPrediction() look like now?
public void Sum()
{
var result = model.SumStack();
viewChannel.Publish(result);
}
public void DoPrediction()
{
var thread = new Thread(InvokeModelWork);
thread.Start();
}
private void InvokeModelWork()
{
var result = model.LongRunningCalculation();
viewChannel.Publish(result);
}
So instead of calling methods on the view we publish messages instead, in this case just an int.
What about the test? We'll need a few changes to SetUp as well, Retlang gives us a nice stubbed implementation of fiber we can use for testing.
[SetUp]
public void SetUp()
{
fiber = new StubFiber();
repository = new MockRepository();
model = repository.StrictMock<Model>();
view = repository.StrictMock<SimpleView>();
SetupResult.For(view.Fibre).Return(fiber);
fiber.Start();
}
[Test]
public void testShouldInvokeLongRunningCalc()
{
var finished = new ManualResetEvent(false);
model.PushNumber(33);
model.PushNumber(44);
model.PushNumber(11);
model.LongRunningCalculation();
LastCall.Return(88);
view.DisplayCurrentTotal(88);
// note the type of m below must match the parameter type of the above method
LastCall.Callback((int m) => finished.Set());
repository.ReplayAll();
var simpleController = new SimpleController(model, view);
simpleController.SendNumber(33);
simpleController.SendNumber(44);
simpleController.SendNumber(11);
simpleController.DoPrediction();
Assert.IsTrue(finished.WaitOne(1000),"Timed out");
repository.VerifyAll();
}
We use a manual reset event to allow us to wait for a particular event to occur, in this case the call to view.DisplayCurrentTotal() is followed by the use of a callback to allow this to happen. We then wait for the event (or a timeout) before calling VerifyAll(). So we can't avoid the fact that things happen asynchronously, but by using a message channel we make things explicitly so and importantly we have consistency across all the controller/view interactions. If we treat our test code as just code we can take advantage of this consistency to extract methods etc and this helps keep the code readable.
Here are the required changes for the view:
private readonly FormFiber formFiber;
public SimpleControl()
{
formFiber = new FormFiber(this, new BatchAndSingleExecutor());
controller = new SimpleController(new DoesMath(),this);
InitializeComponent();
formFiber.Start();
}
public IDisposingExecutor Fibre
{
get { return formFiber; }
}
We no longer need to worry about surrounding things with DispatchRequired() calls, our implementation of the view interface stays nice and clean. We also avoid having to create a view Proxy, this is another solution to the DispatchRequired() that requires a lot of repetitive boiler plate code.
I've tried to keep things simple for this example, some things to think about for a real world implementation are
- That Methods on the view interface will be of the form void Method(Message msg)
- Any methods on the view that do need to return something must be called from the same thread as created the form (but to be thread safe this must be done anyway).
- Whether to multiplex multiple message types over the same channel and then use a Dispatcher to call the correct method on the view or to have a channel per message type
- Whether to create different message channels for different styles of message flow and quality of service
I wonder if this is more Model Channel Controller as opposed to Model View Controller?
In the next entry I'll describe using a similar idea to allow high performance low latency updates of a view direct from a domain model by sidestepping the controller but without seriously compromising encapsulation. In the final entry of the 3 I'll describe how to use Retlang channels to efficiently share work across multiple worker threads and hence CPU core. The Retlang developers have done a great job and created a simple to use but highly useful library.


