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
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:
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
:
private void btnExtendedThread_Click(object sender, EventArgs e)
{
if (_t == null || _t.IsDisposed)
{
_t = new ExtendedThread(new ExtendedThreadArgumentDelegate(ThreadStart),
"argument");
"DispatchToGui", this);
}
if (!_t.IsExecutingFunction)
{
_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
public delegate void ExtendedThreadDelegate();
public delegate void ExtendedThreadArgumentDelegate(object arg);
The Properties
public Thread InnerThread
{
get { return _innerThread; }
}
public bool IsExecutingFunction
{
get { return _executeFunction; }
}
public bool IsReadyToExecute
{
get { return _isReadyToExecute; }
}
public bool IsDisposed
{
get { return (_innerThread == null); }
}
Initializing and Disposing
public ExtendedThread(ExtendedThreadDelegate del)
{
Init(del, null, null);
}
public ExtendedThread(ExtendedThreadDelegate del, Control ctrl)
{
Init(del, null, ctrl);
}
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);
_innerThread.IsBackground = true;
}
public void Dispose()
{
Dispose(0);
}
public void Dispose(int timeout)
{
if (!IsDisposed)
{
_isReadyToExecute = false;
if (timeout < 0)
timeout = 0;
DateTime now = DateTime.Now;
double endTime = new TimeSpan(now.Day, now.Hour, now.Minute,
now.Second, now.Millisecond).TotalMilliseconds + timeout;
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
public void ExecuteFunctionOnceThenPause()
{
_keepExecuting = false;
_delayBetweenExecutions = 1;
ExecuteFunction();
}
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();
}
}
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)
{
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;
}
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.
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.