Click here to Skip to main content
15,888,286 members
Articles / Programming Languages / C#
Article

Extended Threading: Do More with the Same Thread

Rate me:
Please Sign up or sign in to vote.
3.00/5 (5 votes)
5 Jan 2008CPOL3 min read 24.2K   183   15   3
This class is a wrapper around System.Threading.Thread
The difference between the extended thread (above) and a regular thread, both invoking a function -containing a complex calculation- every 100 milliseconds.
The difference between the extended thread (above) and a regular thread, both invoking a function containing a complex calculation every 100 milliseconds.

Introduction

This class I wrote could be classified as an experiment, because normally regular threads are more than enough to do whatever you need to do. Maybe it is best that I give a little background first.

I am currently working on a project where it is needed to monitor the performance of other computers.
Every time the computer to monitor has sent back its performance data, the function to get this data must be called again.
At first I did this in another thread that was created each time data was received. So I thought to myself: "Can't this be done in a more efficient way?", since creating threads is an expensive operation (and my old notebook is very slow).

The class I wrote is a wrapper around System.Threading.Thread. It's purpose is to give you the possibility to execute a function in a different thread, and if you want to do this multiple times, you don't have to make a new thread every time. The advantage is that it performs much better (as making threads is an expensive operation) and is easy to use.
There are two ways of executing: once and then pause or keep executing and you can pause this at any time.
As an extra, there is the possibility to dispatch to the GUI. This is handy if you want to, for instance, change the text of a label on a form.

Background

If you are not familiar with threading in .NET and delegates, here is some documentation as provided by Microsoft:

Quick Overview of the Datamembers

DataMembers

Here you see the datamembers of the ExtendedThread class. As you probably can derive, it implements the interface IDisposable.

Using the Code

If you download the source, I think it will be clear enough to understand how it works. Still it is best that I summarize this:

  • If you look at the code below, you will see that the ExtendedThread class is a wrapper around the regular thread.
  • It uses an inner thread that does the work. Functionality is added to be able to pause the thread.
  • Functions without an argument (ExtendedThreadDelegate) and functions with an argument (ExtendedThreadArgumentDelegate) can be executed as you can see in the constructors.
  • Two ways of executing are available: ExecuteFunctionOnceThenPause () and ExecuteFunctionAndKeepExecuting (int delayBetweenExecutions)
  • Finally it is possible to dispatch to the GUI. This is handy if you want to, for instance, change the text of a label on a form.

Before going over the code in the ExtendedThread class, I will first explain what the demo does.

The Demo

In the demo, you will see that you can compare ExtendedThread with regular threading and the threadpool.
Click one of the buttons to try one of the three ways of executing on another thread. The Windows taskmanager shows the difference in CPU usage between them.

The function that is executed every 100 milliseconds is shown below:

C#
private void ThreadStart(object o)
{
    try
    {
        for (int i = 0; i < 9999; i++)
            new Random(DateTime.Now.Millisecond).NextBytes(new byte[999]);
            if ((o as string) == "DispatchToGui")
                this.Text = "Times executed:" + (++_ul);
    }
    catch { }
}

This is what happens if you click the button to start an ExtendedThread:

C#
private void btnExtendedThread_Click(object sender, EventArgs e)
{
    if (_t == null || _t.IsDisposed)
    {
        _t = new ExtendedThread(new ExtendedThreadArgumentDelegate(ThreadStart),
            "argument");
        //
        //Try this for a demonstration of the dispatching to the Gui.
        //_t = new ExtendedThread(new ExtendedThreadArgumentDelegate(ThreadStart),
            "DispatchToGui", this);
        //
    }
    if (!_t.IsExecutingFunction)
    {
        //Execute every 100 milliseconds.
        _t.ExecuteFunctionAndKeepExecuting(100);
        btnExtendedThread.Text = "Pause";
        btnDispose.Enabled = true;
        btnRegularThread.Enabled = false;
        btnThreadpool.Enabled = false;
    }
    else
    {
        _t.Pause();
        btnExtendedThread.Text = "Continue";
        btnRegularThread.Enabled = true;
        btnThreadpool.Enabled = true;
    }
}

Inside the ExtendedThread Class

The Delegates

C#
//The delegate for a function without arguments.
public delegate void ExtendedThreadDelegate();

//The delegate for a function with an argument.
public delegate void ExtendedThreadArgumentDelegate(object arg);

The Properties

C#
//The thread used to execute the given function,
//this is kept alive until you want to stop it (Dispose () or Dispose (int timeout)).
//When a new object of ExtendedThreading.Thread is made,
//this thread is initiated without specific properties except that
//it is a background thread.
//You can set a few properties of this thread before executing the given function.
public Thread InnerThread
{
    get { return _innerThread; }
}

//Gets if the function is being executed.
public bool IsExecutingFunction
{
    get { return _executeFunction; }
}

//Gets if the inner thread is ready to execute the function.
public bool IsReadyToExecute
{
    get { return _isReadyToExecute; }
}
public bool IsDisposed
{
    get { return (_innerThread == null); }
}

Initializing and Disposing

C#
//To execute a function without an argument.
public ExtendedThread(ExtendedThreadDelegate del)
{
    Init(del, null, null);
}

//Use this constructor if you want to dispatch to the GUI,
//for instance if you want to change the text of a label on a form.
//ctrl is the control (form) you want to dispatch to.
public ExtendedThread(ExtendedThreadDelegate del, Control ctrl)
{
    Init(del, null, ctrl);
}

//To execute a function with an argument.
public ExtendedThread(ExtendedThreadArgumentDelegate del, object arg)
{
    if (arg == null)
        throw new ArgumentNullException("arg");
    Init(del, arg, null);
}
public ExtendedThread(ExtendedThreadArgumentDelegate del, object arg, Control ctrl)
{
    if (arg == null)
        throw new ArgumentNullException("arg");
    Init(del, arg, ctrl);
}
private void Init(Delegate del, object arg, Control ctrl)
{
    if (del == null)
        throw new ArgumentNullException("del");
    _del = del;
    _arg = arg;
    _ctrl = ctrl;
    _innerThread = new Thread(ThreadStart);
    //To make sure the inner thread is disposed when the application is closed.
    _innerThread.IsBackground = true;
}
public void Dispose()
{
    Dispose(0);
}

//timeout is the timeout in milliseconds to let the inner thread 'die'.
//If it is still alive after this timeout, it will be shutdown.
public void Dispose(int timeout)
{
    if (!IsDisposed)
    {
        _isReadyToExecute = false;
        if (timeout < 0)
            timeout = 0;
        //To let the loop end.
        DateTime now = DateTime.Now;
        double endTime = new TimeSpan(now.Day, now.Hour, now.Minute,
            now.Second, now.Millisecond).TotalMilliseconds + timeout;

        //The timeout could be a bit off, because of the calculation of the
        //current total milliseconds.
        //I could have used a partial timeout and let the thread sleep that
        //partial timeout
        //until there has been equally slept as the timeout given with this function
        //or until the thread is not busy anymore,
        //but I find this a bit messy.
        while (_isInnerThreadBusy && new TimeSpan(DateTime.Now.Day, DateTime.Now.Hour,
            DateTime.Now.Minute, DateTime.Now.Second,
            DateTime.Now.Millisecond).TotalMilliseconds < endTime)
        { }
        _executeFunction = false;
        _isInnerThreadBusy = false;
        _innerThread.Abort();
        _innerThread = null;
    }
}

Executing the Given Function

C#
//The function will be executed once.
public void ExecuteFunctionOnceThenPause()
{
    _keepExecuting = false;
    //A minimum delay to keep the thread busy without pushing the CPU to 100%.
    _delayBetweenExecutions = 1;
    ExecuteFunction();
}

//The execution of the function stays in a loop until you pause.
//It is advised to set a delay larger then 0 milliseconds,
//otherwise there will be 100% CPU usage (ASAP execution of the function).
public void ExecuteFunctionAndKeepExecuting(int delayBetweenExecutions)
{
    _keepExecuting = true;
    _delayBetweenExecutions = delayBetweenExecutions;
    if (_delayBetweenExecutions < 0)
        _delayBetweenExecutions = 0;
    ExecuteFunction();
}
private void ExecuteFunction()
{
    if (IsDisposed)
        throw new Exception("The inner thread was disposed.");
    _executeFunction = true;
    if (!_isReadyToExecute)
    {
        _isReadyToExecute = true;
        _innerThread.Start();
    }
}

//Pauses the execution of the function if using ExecuteFunctionAndKeepExecuting ().
public void Pause()
{
    _keepExecuting = false;
}
private void ThreadStart()
{
    _isInnerThreadBusy = true;
    try
    {
        while (_isReadyToExecute)
        {
            if (_executeFunction)
            {
            _executeFunction = _keepExecuting;
            if (_ctrl == null)
                if (_arg == null)
                    (_del as ExtendedThreadDelegate).Invoke();
                else
                    (_del as ExtendedThreadArgumentDelegate).Invoke(_arg);
            else
                DispatchToGui();
            }
            Thread.Sleep(_delayBetweenExecutions);
        }
    }
    catch (Exception ex)
    {
        //Do not throw an exception while disposing.
        if (_isReadyToExecute)
            throw new Exception("The function you want to execute threw an exception.
                \nPlease do your error handling inside that function.\n" + ex.Message);
        _isReadyToExecute = false;
    }
    _executeFunction = false;
    _isInnerThreadBusy = false;
}

//This is handy if you want to change something on the Gui, for instance:
//the text of a label on a form.
//This will only work if a handle for the control is created.
private void DispatchToGui()
{
    if (_ctrl.IsHandleCreated)
    {
        try
        {
            if (_arg == null)
                _ctrl.Invoke(_del);
            else
                _ctrl.Invoke(_del, _arg);
        }
        catch (Exception ex)
        {
            throw new Exception("Could not dispatch to the Gui.\n" + ex.Message);
        }
    }
    else
    {
        throw new Exception("Please make sure a handle for the control (form)
            is created before trying to dispatch to it.");
    }
}

Points of Interest

I think I have covered everything. This is my first article, so any comments about improving it are very welcome.

License

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


Written By
Software Developer HoWest, the university-college of West Flanders
Belgium Belgium
I am currently employed at the Sizing Servers Lab at HoWest, the university-college of West Flanders.
I am lead developer of the stress test tool suite vApus (Virtualized Application Unique Stresstesting). More (a bit outdated) information you can find at http://www.anandtech.com/showdoc.aspx?i=2997&p=5.

Comments and Discussions

 
GeneralWhy not just put a semaphore in front of the thread Pin
robvon8-Jan-08 0:14
robvon8-Jan-08 0:14 
Start the thread, have it wait on a semaphore for work. Terminate it with a flag.

THen you can queue-up requests for the thread and it sleeps when there are no jobs waiting.

The thread pool query would only be relevant if you needed (and your algorithm could utilise) more paralellism, not simply to reduce the overhead of starting and stopping the thread.
GeneralQuestion Pin
danidanidani6-Jan-08 12:36
danidanidani6-Jan-08 12:36 
AnswerRe: Question Pin
Didjeeh7-Jan-08 21:59
Didjeeh7-Jan-08 21:59 

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.