Click here to Skip to main content
Click here to Skip to main content

A common class for executing tasks with a responsive UI

By , 24 Jan 2009
Rate this:
Please Sign up or sign in to vote.

1.jpg

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(); //executes in background worker
          })
          .WhenFinished(businessObject =>
          {
               UseData(businessObject); //executes on UI thread
          })
     .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.

License

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

About the Author

Paul B.

United States United States
I've been a software developer since 1996 and have enjoyed C# since 2003. I have a Bachelor's degree in Computer Science and for some reason, a Master's degree in Business Administration. I currently do software development contracting/consulting.

Comments and Discussions

 
GeneralRe: MVP Pinmemberuffejz25-Jan-09 6:41 
GeneralRe: MVP PinmemberPaul B.25-Jan-09 12:04 
GeneralRe: MVP Pinmemberuffejz26-Jan-09 1:20 
GeneralRe: MVP Pinmemberuffejz29-Jan-09 4:21 
GeneralRe: MVP Pinmemberuffejz19-Feb-09 0:41 
GeneralRe: MVP PinmemberPaul B.19-Feb-09 14:08 
GeneralRe: MVP Pinmemberuffejz23-Feb-09 21:52 
GeneralRe: MVP PinmemberPaul B.12-Mar-09 16:26 
RemoteObjects.ServerMotors need to be a list, not separate variables so you can iterate through them instead of referencing them one at a time
 
UI_Position doesn't belong in ServeroMotor.cs - separate the UI from the servo concept
 
I think you've made this too complicated.
 
Start simple with a unit test and decide how the object model should work, independent from UI. For example, the servos will be raising events to a controller.
 
The controller can reraise the events on a buffered schedule which is variable. You set that buffer based on what you want for the UI, i.e. every 100 events or no more than every x milliseconds, with the last received servo event per servo and the view then displays that info based on those buffered events.
 
The below is what you should get working first in a threaded manner, without worrying about the UI. Once that is working then it should be pretty simple to just hook the UI into the buffered events using the presenter to listen to these.
 
namespace ExampleDesign
{
public class Test
{
public void RunTest()
{
List<ServoMotor> motors = new List<ServoMotor>();
motors.Add(new ServoMotor("Motor1"));
motors.Add(new ServoMotor("Motor2"));
motors.Add(new ServoMotor("Motor3"));
 
//your presenter could use this controller to listen and send data to the UI on the buffered update
ServoMotorController controller = new ServoMotorController(motors, 25);
controller.BufferedUpdated += delegate()
{
foreach (ServoMotor motor in motors)
{
Console.WriteLine(motor.Name + ": " + motor.Property1);
}
};
 
HardwareSimulator simulator = new HardwareSimulator(motors, 100, 10);
simulator.Run();
}
}
 
public class ServoMotorController
{
public ServoMotorController(IEnumerable<ServoMotor> motors, int bufferCount)
{
foreach(ServoMotor motor in motors)
motor.HardwareUpdated += new ServoMotor.HardwareUpdatedDelegate(motor_HardwareUpdated);
 
this.bufferCount = bufferCount;
}
 
int bufferCount = 0;
int events = 0;
 
public delegate void BufferedUpdatedDelegate();
public event BufferedUpdatedDelegate BufferedUpdated;
 
void motor_HardwareUpdated()
{
events++;
 
//only raise every bufferCount events
if (events % bufferCount == 0)
{
BufferedUpdated();
}
}
}
 
public class ServoMotor
{
public ServoMotor(string name)
{
this.Name = name;
}
 
private string _Name;
 
public string Name
{
get { return _Name; }
set { _Name = value; }
}
 

private int _Property1;
public int Property1
{
get { return _Property1; }
set { _Property1 = value; }
}
 
public delegate void HardwareUpdatedDelegate();
public event HardwareUpdatedDelegate HardwareUpdated;
 
public void Update(object someData) //not object, something else with whatever data
{
Property1 = new Random().Next();
 
if(HardwareUpdated != null)
HardwareUpdated();
}
}
 
public class HardwareSimulator
{
public HardwareSimulator(IEnumerable<ServoMotor> motors, int eventCount, int delayMilliseconds)
{
this.motors = motors;
this.eventCount = eventCount;
this.delayMilliseconds = delayMilliseconds;
}
 
IEnumerable<ServoMotor> motors = null;
int eventCount;
int delayMilliseconds;
 
public void Run()
{
for (int i = 0; i < eventCount; i++)
{
foreach (ServoMotor motor in motors)
{
motor.Update("Some stuff");
}
 
Thread.Sleep(delayMilliseconds);
}
}
}
}
GeneralRe: MVP [modified] Pinmemberuffejz15-Mar-09 1:44 
GeneralRe: MVP PinmemberPaul B.15-Mar-09 3:47 
GeneralRe: MVP [modified] Pinmemberuffejz15-Mar-09 4:27 
GeneralRe: MVP PinmemberPaul B.15-Mar-09 12:28 
GeneralRe: MVP [modified] Pinmemberuffejz16-Mar-09 3:02 
GeneralRe: MVP Pinmemberuffejz11-Mar-09 22:58 
GeneralRe: MVP PinmemberPaul B.12-Mar-09 3:11 
GeneralRe: MVP Pinmemberuffejz12-Mar-09 3:25 
GeneralRe: MVP Pinmemberuffejz18-Mar-09 23:48 
GeneralRe: MVP Pinmemberuffejz19-Mar-09 0:37 

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

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

| Advertise | Privacy | Mobile
Web03 | 2.8.140415.2 | Last Updated 24 Jan 2009
Article Copyright 2009 by Paul B.
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid