Introduction
This is a common way to implement the standard WinForms behavior of executing a potentially long running action while still having the UI be responsive and allowing the user to know that something is happening via the UI. This can be used with standard WinForms, WPF, or an MVP style application which is how I use it.
Background
Some of the other options which I felt weren't as easy as this to use:
Using the code
Download the source and hit F5 - three examples are given:
- The standard behavior where the UI locks while executing.
- The new behavior where the UI is not locked and the user can tell the form is busy.
- What happens in the new behavior when an exception is thrown.
In order to use this, the form or view in question must implement the interface IThreadedExecuterView
, in either the form itself or in a base form:
public partial class Form1 : Form, IThreadedExecuterView
{
#region IThreadedExecuterView Members
public void SetWait(bool isEnabled)
{
this.Cursor = (isEnabled ? Cursors.Default : Cursors.WaitCursor);
button1.Enabled = button2.Enabled = isEnabled;
}
public void HandleException(Exception ex)
{
MessageBox.Show("This is your standard error " +
"handling call here for " + ex.Message);
}
Below is an example of it in use - there is no need to worry about the UI thread, creating delegates, or doing anything special for exceptions other than handling them in one place.
using (ThreadedExecuter<BusinessObject> executer =
new ThreadedExecuter<BusinessObject>(this))
{
executer
.Process(() =>
{
return GetData();
})
.WhenFinished(businessObject =>
{
UseData(businessObject);
})
.Run();
}
It works for value types or reference types. Here is a Unit Test demonstrating its usage:
[Test]
public void TestThreadedExecuterNormalBehavior()
{
int result = 0;
bool didComplete = false;
AutoResetEvent waiter = new AutoResetEvent(false);
IThreadedExecuterView view =
MockRepository.GenerateStub<IThreadedExecuterView>();
using (ThreadedExecuter<int> worker = new ThreadedExecuter<int>(view))
{
worker
.Process(()=>
{
Thread.Sleep(1000);
return 42;
})
.WhenFinished(workerResult =>
{
result = workerResult;
didComplete = true;
waiter.Set();
})
.Run();
}
waiter.WaitOne(2000, false);
Assert.AreEqual(42, result);
Assert.IsTrue(didComplete);
}
History
- 24-Jan-2009 - Initial version.