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

Simple Threading

Rate me:
Please Sign up or sign in to vote.
4.03/5 (17 votes)
7 Aug 2007CPOL8 min read 108.5K   1.4K   84   30
Threading for beginners

Screenshot - threadSample.jpg

Introduction

This is a simple example taken from MSDN about multi-threading or, the way I like to say it, "Doing two things at once." I've modified the original example to make it easier to see how everything interacts. Easier for me anyway, since we are all different. One explanation may be adequate for some and not for others. I hope this can shine some light for some, as it did for me.

Far from being any type of Guru on the subject, I'll try and explain it the best I can from my point of view. I honestly don't understand everything about threads, delegates and so on. The important thing in my mind is that I understood enough to have it do what I wanted in the end. The next step is understanding the rest.

What the Code Does

On start-up, a thread is fired that updates a label counting from 0 to 999. Buttons on the form give you the ability to start another thread that does the same thing, only at a slower pace. This shows you that both threads are separate from one another.

Background

Well, I guess it all started when I wanted to do two things at once, say, look up files in a directory. I wanted to have a nice progress bar move up and down or at least be able to move the Window around while it's doing it. If there's something I hate, it's not knowing if the application in front of me is alive or dead. After hours and hours of getting the error message, "Cross-thread operation not valid," I figured my way around it.

Updated

  • 20-07-2007
    Ok, so I tried to make good on my word to follow the advice and comments from people nice enough to point out bad habits. One example is Thread.Abort(). I tried several things and attempted to understand them as best I could. What I came up with was something that does exactly the same thing, but without Thread.Abort(). I hope this is a good thing. I haven't gotten any errors and believe me, I tried to click everywhere and at different moments to get one. An error I did get was, "Could not access an object that was in the process of disposing itself." In my case, this is the label I'm constantly trying to modify. I hope the if statement helps. If not, I'm sure someone will point it out. Anyway, I'd like to consider this a work in progress. By getting constructive comments, I'll be in a better position to make changes, learn and hopefully help others. This is, after all, a helpful community. I left the original code fragments and pasted the new code right under it so that people can compare the two. I hope I did better this time than the last.
  • 25-07-2007
    Another update. A few people mentioned that it would be better to use BeginInvoke() rather than Invoke(). Using Invoke() makes a synchronous call to the thread, causing the thread itself to wait until the GUI is updated before continuing on its way. BeginInvoke(), on the other hand, makes that call asynchronously. This means that the thread will continue working without waiting for the GUI to update itself. In this case, that is the label's Text property. So if for some reason the GUI hangs, my counter will keep on working without waiting for the label to update its text property from, say, 2 to 3 and so on. For a more concrete explanation, I suggest -- as it was to me -- reading the article here by Mr. S. Senthil Kumar. I also suggest reading the comments at the end of this page from the other users. This way you'll better understand the changes and why I made them.
  • 07-08-2007
    Another update. Got some good suggestions and a typo to fix up. First of all, thanks to everyone that has contributed. I'm disappointed in myself for not keeping the original article and seeing its evolution. However, the original code from all versions are part of the download. So, if you want to see the changes in the code itself, extract them all and check out the differences. It has be brought to my attention that I should check if a thread is stopped gracefully and, if not, force Abort() on it. This gives a certain amount of time to the thread to join. If for some reason it doesn't do so, it will kill it with Abort(). The code in has been changed accordingly and, as always, you should read up on the comments made by users, since the explanation is quite clear on the reasons to do so. A typo has been corrected thanks to Don Driskell; I was assigning null twice on a thread. I guess that's it then! I don't see much more that can be done without re-writing the whole article. Of course, I'm still trying learn and understand threads. Over time, I'll try and find the time to write another article. Threads are not as simple as portrayed here, but it's a start.

Code Fragments

Ok, let's start with the references at the top of the file. When you start a new project, be sure to add System.Threading with the others. If not, the only thread that will run will be the main application.

C#
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;

Secondly, you'll need to declare a delegate and some threads. Think of them like this: The threads are little bits of code that will run parallel to each other, much like sprinters in the 100 meter dash. So, Thread threadLeft is runner 1 and Thread threadRight is runner 2. As for the delegate, well I try and look at it this way: It is a function that can be attached and executed in a thread other than the main thread. A better way to think of it may be that they are like pointers in C and C++.

C#
delegate void SetTextCallback(string text);
private Thread threadLeft = null;
private Thread threadRight = null;
private volatile bool _stopLeft = false; #added this

Ok, now let's have a thread run on start-up. It's actually surprising how much code you need to do this. In my example, I have a label that will count from 0 to 999, all the while still being able interact with the form. For example, click a button and move it around. These are the parts you need. I'll have threadleft start first.

C#
public Form1()
{
    InitializeComponent();this.threadLeft =
        new Thread(new ThreadStart(this.ThreadProcSafe));
    this.threadLeft.Start();
    btnStartLeft.Text = "Stop Left";
}

So, what we have here is pretty simple. InitializeComponent(); prepares all of your labels, buttons, etc. When that's done, it moves on to the new thread. The simplest way to explain it is this way, which is the way in which I understand it: Create a new instance of threadleft. When it's started, fire off the ThreadProcSafe() function. Let's look at that function now.

The code below has been changed from this:

C#
private void ThreadProcSafe()
{
    for (int x = 0; x < 1000; x++)
    {
        this.SetText(x.ToString());
        Thread.Sleep(600);
    }
}

To this:

C#
private void ThreadProcSafe()
{
    int x = 0;
    while(!_stopLeft)
    {
        this.SetText((x++).ToString());
        Thread.Sleep(600);
    }
}

Once _stopLeft is true, the loop is exited and stops calling the function that changes lbl_left's Text property. ThreadProcSafe() is a very simple while loop. Nothing fancy, it just increments an integer by 1 and finishes once _stopLeft equals true. Thread.Sleep(600); in the loop is used to slow the counting process down. Since ThreadProcSafe() is in a separate thread, sleep only affects the while loop. It's also calling another function, SetText(string text). This is where the delegate comes in.

The code below has been changed from this:

C#
private void SetText(string text)
{
    if (this.lbl_left.InvokeRequired)
    {
        SetTextCallback d = new SetTextCallback(SetText);
        this.Invoke(d, new object[] { text });
    }
    else
    {
        this.lbl_left.Text = text;
    }
}

To this:

C#
private void SetText(string text)
{
    try
    {
        //If needs to be invoked, invoke it then
        //call the delegate
        if (!lbl_right.Disposing)
        {
            if (this.lbl_left.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(SetText);
                //this.Invoke(d, new object[] { text });
                this.BeginInvoke(d, new object[] { text });
            // ***Replaced the Invoke line with BeginInvoke,
            // making it asynchronous***
            }
            //If already invoked then update label with new value.
            else
            {
                this.lbl_left.Text = text;
            }
        }
    }
    catch (Exception e)
    {
        MessageBox.Show(e.Message);
    }
}

If I paraphrase the online MSDN, they say this about the Invoke method: It provides access to properties and methods that have been exposed by an object. Well, the object exposed in our case will be the label called lbl_left. Then we'll have access to its properties, such as Text. So, threadLeft starts and calls ThreadProcSafe, in which the while loop calls SetText. Once we have the ability to change the label's properties, we change Text to our integer from our loop. This will go on until you stop the application or kill the thread somehow. Since it's in its own thread, we can make a button to stop it. Here's the bit of code to do so.

The code below has been changed from this:

C#
if (this.threadLeft != null && this.threadLeft.IsAlive)
{
    this.threadLeft.Abort(); //Very, very BAD!
    this.threadLeft = null;
    SetText("Left");
    btnStartLeft.Text = "Start Left";
}

To this:

C#
if (!_stopLeft)
{
    _stopLeft = true;
    if (!this.threadLeft.Join(1000))
    {
        this.threadLeft.Abort();
    }
    SetText("Left");
    btnStartLeft.Text = "Start Left";
}

Slap this in a button, in this case, the left button. It's actually pretty easy to understand. The reason for the if statement is that I have the start and stop functions associated with the same button. If the thread is not running, only having this.threadLeft.IsAlive as a condition will throw the message, "Object reference not set to an instance of an object." This basically means that it doesn't exist or is not instantiated with new. The way around it is to check if it's null. I know this.threadLeft = null; is probably overkill on my part, but I just want to make sure. Have a look at the code for the right button and you'll see that it's pretty much the same thing.

Points of Interest

If you still have threads running when you click the "X" on your form, they may not stop correctly. Override OnClose and slap it some code to check if any threads are running. This way, when you close the application with the "X," you are sure to stop the threads before closing the application.

The code below has been changed from this:

C#
protected override void OnClosed(EventArgs e)
{
    //Check if thread on the Left is alive
    //If alive (or not null) stop it.
    if (this.threadLeft != null && this.threadLeft.IsAlive)
    {
        this.threadLeft.Abort();
        this.threadLeft = null;
    }
    base.OnClosed(e);
}

To this:

C#
protected override void OnClosed(EventArgs e)
}
    _stopLeft = true;
    _stopRight = true;
    if (!this.threadRight.Join(1000)) 
    //1000 milliseconds to execute one loop
    {
        this.threadRight.Abort(); 
        //If it takes more than 1000ms then Abort is called
    }
    if (!this.threadLeft.Join(1000))
    {
        this.threadLeft.Abort();
    }
    base.OnClosed(e);
}

The thread is given 1000 milliseconds to execute one loop, hit the flag that says to stop and exit gracefully. If it takes more than 1000ms, then Abort() is called on the thread to kill it forcefully. Thanks, J4amieC, you explained it well.

History

  • It all started with the Big Bang!
  • Then on July 18th, 2007 I posted the original version of this article.
  • After reading up and taking in all the comments, on July 27th, 2007 I tried to better myself and make this thing work.
  • On August 7th, I gave the threads time to join, if not abort.

License

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


Written By
Canada Canada
Studied English Teaching as a second language at university here in Montreal(you wouldn't know it the way I type would ya), after that did some networking course with a bit of programming. Mostly working as a network admin these days.
I also like to write short stories, and draw..mostly black and white stuff...

Comments and Discussions

 
GeneralRe: Invoke is synchronous Pin
K R Mellor24-Jul-07 1:51
K R Mellor24-Jul-07 1:51 
GeneralRe: Invoke is synchronous Pin
loneferret24-Jul-07 2:11
loneferret24-Jul-07 2:11 
GeneralRe: Invoke is synchronous Pin
Mufaka24-Jul-07 19:20
Mufaka24-Jul-07 19:20 
GeneralRe: Invoke is synchronous Pin
loneferret25-Jul-07 2:04
loneferret25-Jul-07 2:04 
GeneralRe: Invoke is synchronous Pin
Dave Moor (the real one)31-Jul-07 0:23
Dave Moor (the real one)31-Jul-07 0:23 
GeneralRe: Invoke is synchronous Pin
loneferret31-Jul-07 0:58
loneferret31-Jul-07 0:58 
GeneralRe: Invoke is synchronous Pin
Shukaido14-Nov-07 12:43
Shukaido14-Nov-07 12:43 
Generalbetter use a other construction Pin
Landarzar18-Jul-07 23:53
Landarzar18-Jul-07 23:53 
GeneralRe: better use a other construction Pin
loneferret21-Jul-07 0:08
loneferret21-Jul-07 0:08 
GeneralThread.Abort() Pin
wb18-Jul-07 9:32
wb18-Jul-07 9:32 
GeneralRe: Thread.Abort() Pin
jconwell18-Jul-07 9:57
jconwell18-Jul-07 9:57 
GeneralRe: Thread.Abort() Pin
loneferret18-Jul-07 10:17
loneferret18-Jul-07 10:17 
GeneralRe: Thread.Abort() Pin
Xaroth18-Jul-07 12:27
Xaroth18-Jul-07 12:27 
GeneralRe: Thread.Abort() Pin
Axel Rietschin18-Jul-07 15:31
professionalAxel Rietschin18-Jul-07 15:31 
GeneralRe: Thread.Abort() Pin
loneferret18-Jul-07 16:12
loneferret18-Jul-07 16:12 
GeneralRe: Thread.Abort() Pin
Axel Rietschin18-Jul-07 16:49
professionalAxel Rietschin18-Jul-07 16:49 
GeneralRe: Thread.Abort() [modified] Pin
loneferret20-Jul-07 23:56
loneferret20-Jul-07 23:56 
GeneralRe: Thread.Abort() Pin
merlin98127-Jul-07 3:32
professionalmerlin98127-Jul-07 3:32 
GeneralRe: Thread.Abort() Pin
J4amieC27-Jul-07 4:42
J4amieC27-Jul-07 4:42 
GeneralRe: Thread.Abort() Pin
loneferret21-Jul-07 0:24
loneferret21-Jul-07 0:24 

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.