Click here to Skip to main content
15,885,365 members
Articles / Desktop Programming / Windows Forms

A common class for executing tasks with a responsive UI

Rate me:
Please Sign up or sign in to vote.
4.74/5 (21 votes)
24 Jan 2009CPOL1 min read 93.1K   677   81   58
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:

C#
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.

C#
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:

C#
[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)


Written By
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, Pin
john_max5-Dec-09 9:44
john_max5-Dec-09 9:44 
GeneralHi paul Thanks Pin
Danie de Kock27-Jul-09 21:47
Danie de Kock27-Jul-09 21:47 
GeneralNice mechanism Pin
colonel72023-Jun-09 10:54
colonel72023-Jun-09 10:54 
GeneralRe: Nice mechanism Pin
Paul B.23-Jun-09 11:10
Paul B.23-Jun-09 11:10 
Questionvoid as a return type? Pin
uffejz22-Apr-09 22:23
uffejz22-Apr-09 22:23 
AnswerRe: void as a return type? [modified] Pin
Danie de Kock27-Jul-09 21:46
Danie de Kock27-Jul-09 21:46 
GeneralRe: void as a return type? Pin
uffejz27-Jul-09 23:04
uffejz27-Jul-09 23:04 
GeneralRe: void as a return type? Pin
Danie de Kock27-Jul-09 23:29
Danie de Kock27-Jul-09 23:29 
GeneralRe: void as a return type? Sorry Pin
Danie de Kock27-Jul-09 23:33
Danie de Kock27-Jul-09 23:33 
GeneralRe: void as a return type? Sorry Pin
uffejz27-Jul-09 23:40
uffejz27-Jul-09 23:40 
GeneralRe: void as a return type? Sorry Pin
Danie de Kock27-Jul-09 23:46
Danie de Kock27-Jul-09 23:46 
GeneralRe: void as a return type? Sorry Pin
uffejz27-Jul-09 23:54
uffejz27-Jul-09 23:54 
GeneralRe: void as a return type? Pin
uffejz27-Jul-09 23:51
uffejz27-Jul-09 23:51 
AnswerThank you Pin
Danie de Kock27-Jul-09 23:53
Danie de Kock27-Jul-09 23:53 
GeneralRe: Thank you Pin
uffejz28-Jul-09 2:30
uffejz28-Jul-09 2:30 
Would be nice if the code could be documented a bit better, something like this maybe?

Microsft StyleCop or some other code rule design program.
//------------------------------------------------------------------------------
// <copyright 
//  file="ThreadedExecuter.cs" 
//  version="1.0">
// </copyright>
//
// <author></author>
//------------------------------------------------------------------------------
namespace Library
{
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.ComponentModel;

    /// <summary>
    /// This class provides a background thread executer
    /// </summary>
    /// <example>
    /// using (ThreadedExecuter executer = new ThreadedExecuter(this))
    /// {
    ///     executer
    ///         .Process(() =>
    ///         {
    ///             int foo = 0;
    ///         })
    ///         .WhenFinished(() =>
    ///             {
    ///                 this.uxFoo.Text = "";
    ///             }
    ///         )
    ///         .Run();
    /// }
    /// </example> 
    public class ThreadedExecuter : IDisposable
    {
        #region Fields
        // The background worker for ThreadedExecuter class
        private BackgroundWorker worker;

        // 
        private IThreadedExecuterView view;

        //
        private Action finallyDelegate;

        // Indicates if this object is disposed or not
        private bool disposed;
        #endregion

        #region Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="ThreadedExecuter"/> class.
        /// </summary>
        public ThreadedExecuter()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ThreadedExecuter"/> class.
        /// </summary>
        /// <param name="View"></param>
        public ThreadedExecuter(IThreadedExecuterView view)
        {
            this.view = view;
        }
        #endregion

        #region Destructor
        /// <summary>
        /// Finalizes an instance of the <see cref="ThreadedExecuter"/> class
        /// </summary>
        ~ThreadedExecuter()
        {
            Dispose(false);
        }

        /// <summary>
        /// Dispose <see cref="ThreadedExecuter"/>
        /// </summary>
        public void Dispose()
        {
            Dispose(true);

            // Tell the garbage collector not to call the finalizer
            // since all the cleanup will already be done.
            GC.SuppressFinalize(true);
        }

        /// <summary>
        /// Disposes managed and unmanaged resources for the <see cref="ThreadedExecuter"/>
        /// </summary>
        /// <param name="disposing">Dispose managed resources or not</param>
        private void Dispose(bool disposing)
         {
             if (this.disposed)
             {
                 return;
             }

             if (disposing)
             {
                 // Free any managed resources in this section
                 if (worker != null)
                 {
                     worker.Dispose();
                 }
             }

             // Free any unmanaged resources in this section
             this.disposed = true;
         }
        #endregion

        #region Methods
        /// <summary>
        /// This method handel's the code the needs to be processed in the background thread.   
        /// </summary>
        /// <remarks>
        /// For value types use default instead of null, since null won't be valid when casting
        /// </remarks>
        /// <param name="GetResultsAction"></param>
        /// <returns></returns>
        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;
        }

        /// <summary>
        /// Provides a way to execute actions after the thread has finished 
        /// </summary>
        /// <param name="ProcessResultsAction"></param>
        /// <returns>The thread executer object</returns>
        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;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="FinallyDelegate"></param>
        /// <returns>The thread executer object</returns>
        public ThreadedExecuter Finally(Action FinallyDelegate)
        {
            this.finallyDelegate = FinallyDelegate;

            return this;
        }

        /// <summary>
        /// Executes the <see cref="ThreadedExecuter"/>
        /// </summary>
        /// <returns>The thread executer object</returns>
        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;
        }
        #endregion
    }
}

GeneralRe: Thank you Pin
Danie de Kock28-Jul-09 3:43
Danie de Kock28-Jul-09 3:43 
GeneralRe: Thank you Pin
uffejz28-Jul-09 4:11
uffejz28-Jul-09 4:11 
GeneralRe: Thank you Pin
Danie de Kock28-Jul-09 4:35
Danie de Kock28-Jul-09 4:35 
GeneralRe: Thank you Pin
uffejz28-Jul-09 12:17
uffejz28-Jul-09 12:17 
GeneralRe: Thank you Pin
Danie de Kock28-Jul-09 20:27
Danie de Kock28-Jul-09 20:27 
Generalgood class Pin
Donsw19-Feb-09 14:52
Donsw19-Feb-09 14:52 
GeneralKill thread Pin
mgrounds11-Feb-09 4:35
mgrounds11-Feb-09 4:35 
GeneralRe: Kill thread Pin
mgrounds11-Feb-09 23:07
mgrounds11-Feb-09 23:07 
GeneralRe: Kill thread Pin
mgrounds12-Feb-09 5:54
mgrounds12-Feb-09 5:54 
GeneralRe: Kill thread Pin
Paul B.14-Feb-09 3:10
Paul B.14-Feb-09 3:10 

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.