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

A common class for executing tasks with a responsive UI

, 24 Jan 2009
Rate this:
Please Sign up or sign in to vote.
Execute actions while making the form wait and still be responsive to other tasks.

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

 
GeneralThanx, Pinmemberjohn_max5-Dec-09 9:44 
GeneralHi paul Thanks PinmemberDanie de Kock27-Jul-09 21:47 
GeneralNice mechanism Pinmembercolonel72023-Jun-09 10:54 
GeneralRe: Nice mechanism PinmemberPaul B.23-Jun-09 11:10 
Questionvoid as a return type? Pinmemberuffejz22-Apr-09 22:23 
AnswerRe: void as a return type? [modified] PinmemberDanie de Kock27-Jul-09 21:46 
GeneralRe: void as a return type? Pinmemberuffejz27-Jul-09 23:04 
GeneralRe: void as a return type? PinmemberDanie de Kock27-Jul-09 23:29 
GeneralRe: void as a return type? Sorry PinmemberDanie de Kock27-Jul-09 23:33 
O i c your problem Laugh | :laugh: Laugh | :laugh:
heres a better view
 
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
 
namespace Library
{
	// Void
	//using (ThreadedExecuter executer = new ThreadedExecuter(this))
	//{
	//    executer
	//        .Process(() =>
	//        {
	//            int asdfa = 0;

	//        })
	//        .WhenFinished(() =>
	//            {
	//                this.Text = "";
	//            }
	//        )
	//        .Run();
	//}

	// Int/String //enige object
	//using (ThreadedExecuter<int> worker = new ThreadedExecuter<int>(view))
	//{
	//    worker
	//         .Process(() =>
	//         {
	//             Thread.Sleep(1000);
	//             return 42;
	//         })
	//         .WhenFinished(workerResult =>
	//         {
	//             int i = workerResult;

	//         })
	//    .Run();
	//}
	public interface IThreadedExecuterView
	{
		void SetWait(bool isEnabled);
		void HandleException(Exception ex);
	}
	public delegate void ThreadedExecuterDelegate();
	public class ThreadedExecuter : IDisposable
	{
		public ThreadedExecuter()
		{
		}
 
		public ThreadedExecuter(IThreadedExecuterView View)
		{
			this.View = View;
		}
 
		BackgroundWorker worker = null;
		IThreadedExecuterView View = null;
 
		//for value types use default instead of null since won't be valid when casting, will alows us to use int, etc. in addition to classes

 
		public ThreadedExecuter Process(ThreadedExecuterDelegate GetResultsAction)
		{
			View.SetWait(false);
 
			worker = new BackgroundWorker();
 
			worker.DoWork += new DoWorkEventHandler(delegate(object sender, DoWorkEventArgs e)
			{
				try
				{
					GetResultsAction();
				}
				catch (Exception ex)
				{
					View.HandleException(ex);
				}
			});
 
			return this;
		}
 
		public ThreadedExecuter WhenFinished(Action ProcessResultsAction)
		{
			worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(delegate(object sender, RunWorkerCompletedEventArgs e)
			{
				//if any end action and want to continue, call it
				//want the results action to continue to wait until done
				//but want post action to not be within wait
				try
				{
					ProcessResultsAction();
				}
				finally
				{
					//don't ever want to allow this to get an exception so we can get endinvoke exception
					//want to make sure we stop from waiting, even on error
					try
					{
						View.SetWait(true);
					}
					catch (Exception ex)
					{
						Console.WriteLine("Error setting view wait: " + ex.ToString());
					}
				}
			});
			return this;
		}
 
		Action FinallyDelegate = null;
 
		public ThreadedExecuter Finally(Action FinallyDelegate)
		{
			this.FinallyDelegate = FinallyDelegate;
			return this;
		}
 
		public ThreadedExecuter Run()
		{
			//callpost action if any
			if (FinallyDelegate != null)
			{
				worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(delegate(object sender, RunWorkerCompletedEventArgs e)
				{
					try
					{
						FinallyDelegate();
					}
					catch (Exception ex)
					{
						View.HandleException(ex);
					}
				});
			}
 
			worker.RunWorkerAsync();
 
			return this;
		}
 
		public void Dispose()
		{
			if (worker != null)
				worker.Dispose();
		}
 
	}
 

 
	public delegate T ThreadedExecuterDelegate<T>();
 
	/// <summary>
	/// Class to encapsulate frequently needed action in background thread with hourglass and disabled form,
	/// then use results frombackground thread, then do something immediately after
	/// </summary>
	/// <typeparam name="T"></typeparam>
	public class ThreadedExecuter<T> : IDisposable
	{
		public ThreadedExecuter()
		{
		}
 
		public ThreadedExecuter(IThreadedExecuterView View)
		{
			this.View = View;
		}
 
		BackgroundWorker worker = null;
		IThreadedExecuterView View = null;
 
		//for value types use default instead of null since won't be valid when casting, will alows us to use int, etc. in addition to classes
		T actionValue = default(T);
 
		public ThreadedExecuter<T> Process(ThreadedExecuterDelegate<T> GetResultsAction)
		{
			View.SetWait(false);
 
			worker = new BackgroundWorker();
 
			worker.DoWork += new DoWorkEventHandler(delegate(object sender, DoWorkEventArgs e)
			{
				try
				{
					e.Result = GetResultsAction();
				}
				catch (Exception ex)
				{
					View.HandleException(ex);
				}
			});
 
			return this;
		}
 
		public ThreadedExecuter<T> WhenFinished(Action<T> ProcessResultsAction)
		{
			worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(delegate(object sender, RunWorkerCompletedEventArgs e)
			{
				if (e != null && e.Result != null)
					actionValue = (T)e.Result;
 
				//if any end action and want to continue, call it
				//want the results action to continue to wait until done
				//but want post action to not be within wait
				try
				{
					ProcessResultsAction(actionValue);
				}
				finally
				{
					//don't ever want to allow this to get an exception so we can get endinvoke exception
					//want to make sure we stop from waiting, even on error
					try
					{
						View.SetWait(true);
					}
					catch (Exception ex)
					{
						Console.WriteLine("Error setting view wait: " + ex.ToString());
					}
				}
			});
 
			return this;
		}
 
		Action FinallyDelegate = null;
 
		public ThreadedExecuter<T> Finally(Action FinallyDelegate)
		{
			this.FinallyDelegate = FinallyDelegate;
			return this;
		}
 
		public ThreadedExecuter<T> Run()
		{
			//callpost action if any
			if (FinallyDelegate != null)
			{
				worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(delegate(object sender, RunWorkerCompletedEventArgs e)
				{
					try
					{
						FinallyDelegate();
					}
					catch (Exception ex)
					{
						View.HandleException(ex);
					}
				});
			}
 
			worker.RunWorkerAsync();
 
			return this;
		}
 
		public void Dispose()
		{
			if (worker != null)
				worker.Dispose();
		}
 
	}
 
}

GeneralRe: void as a return type? Sorry Pinmemberuffejz27-Jul-09 23:40 
GeneralRe: void as a return type? Sorry PinmemberDanie de Kock27-Jul-09 23:46 
GeneralRe: void as a return type? Sorry Pinmemberuffejz27-Jul-09 23:54 
GeneralRe: void as a return type? Pinmemberuffejz27-Jul-09 23:51 
AnswerThank you PinmemberDanie de Kock27-Jul-09 23:53 
GeneralRe: Thank you Pinmemberuffejz28-Jul-09 2:30 
GeneralRe: Thank you PinmemberDanie de Kock28-Jul-09 3:43 
GeneralRe: Thank you Pinmemberuffejz28-Jul-09 4:11 
GeneralRe: Thank you PinmemberDanie de Kock28-Jul-09 4:35 
GeneralRe: Thank you Pinmemberuffejz28-Jul-09 12:17 
GeneralRe: Thank you PinmemberDanie de Kock28-Jul-09 20:27 
Generalgood class PinmemberDonsw19-Feb-09 14:52 
GeneralKill thread Pinmembermgrounds11-Feb-09 4:35 
GeneralRe: Kill thread Pinmembermgrounds11-Feb-09 23:07 
GeneralRe: Kill thread Pinmembermgrounds12-Feb-09 5:54 
GeneralRe: Kill thread PinmemberPaul B.14-Feb-09 3:10 
GeneralRe: Kill thread Pinmembermgrounds15-Feb-09 23:47 
GeneralRe: Kill thread PinmemberPaul B.16-Feb-09 4:39 
GeneralRe: Kill thread Pinmemberuffejz22-Apr-09 22:35 
GeneralRe: Kill thread PinmemberDanie de Kock28-Jul-09 23:06 
GeneralRe: Kill thread PinmemberDanie de Kock28-Jul-09 23:24 
GeneralUnit Test Code Fails PinmemberHenrik Jonsson27-Jan-09 9:06 
GeneralRe: Unit Test Code Fails PinmemberPaul B.27-Jan-09 15:31 
GeneralRe: Unit Test Code Fails PinmemberHenrik Jonsson28-Jan-09 8:26 
GeneralMVP usage PinmemberPaul B.26-Jan-09 4:23 
GeneralMy vote of 2 PinmemberProJester125-Jan-09 8:03 
GeneralRe: My vote of 2 PinmemberPaul B.25-Jan-09 11:57 
GeneralRe: My vote of 2 PinmemberPaul B.25-Jan-09 12:00 
GeneralRe: My vote of 2 PinmemberJon_Boy20-May-09 9:39 
GeneralMVP Pinmemberuffejz25-Jan-09 3:04 
GeneralRe: MVP PinmemberPaul B.25-Jan-09 4:08 
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 
GeneralRe: MVP [modified] Pinmemberuffejz15-Mar-09 1:44 
GeneralRe: MVP PinmemberPaul B.15-Mar-09 3:47 

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
Web01 | 2.8.140721.1 | Last Updated 24 Jan 2009
Article Copyright 2009 by Paul B.
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid