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

How to Properly Handle Cross-thread Events and Update a Label with BeginInvoke and BackgroundWorker

, 8 Feb 2008 LGPL3
Rate this:
Please Sign up or sign in to vote.
This article explains how to update a Label using cross-thread event handling, delegates, BeginInvoke and BackgroundWorker.

Introduction

This article proposes a solution for updating Windows Form components when using cross-thread method calls.

Since .NET Framework 2.0, you can't make an explicit method call to a Windows Form component method (say, you wanted to alter the text of a label). This happens because Windows Forms is not thread-safe (in other words, it can't guarantee that the behavior using threads will be the same as using only the main program thread).

What to do then, when your beautiful, multi-core optimized code has an user interface that needs to be updated from whatever thread is needed? You have to use the BeginInvoke method, so you can make a thread-safe call.

The article covers basically three areas:

  1. Creating and handling custom events
  2. Cross-thread method call
  3. BackgroundWorker class

Note: English is not my mother language, so the text may not be accurate sometimes.

Background

To understand this code, I recommend that you have at least some contact with the C# language (the more, the better).

If you have already worked with event handling before, this tutorial may improve your skills.

If you never had the chance or patience to learn how to implement a custom event or how to use it with multiple threads, this is specially for you.

Using the Code

The code is fully documented. But for those who are in a hurry and seek just some pointers and advice, I'll give a brief explanation.

The code consists basically of two classes:

  1. Door.cs
  2. Form1.cs

The Door class has the delegates and events declaration. I have opted to include a BackgroundWorker to insert some delay between changing the Door state from closed to open and vice-versa.

The Form1 class is a Windows Form with two buttons (Open door and Close door) and a label to display the current status. The door starts up closed, so you have to click Open to raise two events.

The first event raised is the event telling that the door is opening. The second one is raised when the door is open (when the background worker finishes its job).

By design, you can't close a door that isn't open and can't open a door that isn't closed (the in-between events can't be altered even if you click the other button).

Now some code fun.

The following code is inside the Door class:

// event raised before the door opens
public delegate void OpeningDoor ();
public event OpeningDoor RaiseDoorOpening;

// event raised after the door is open
public delegate void OpenDoor ();
public event OpenDoor RaiseDoorOpened;

// event raised before the door closes
public delegate void ClosingDoor ();
public event ClosingDoor RaiseDoorClosing;

// event raised after the door is closed
public delegate void ClosedDoor ();
public event ClosedDoor RaiseDoorClosed;

And now the Open method for the Door class:

public void Open ()
{
    // Asserts that the door isn't already open
    if ( !_isOpen )
    {
        RaiseDoorOpening (); // raises event before opening door

        #region Open door delay

        // we're going to insert a small asynchronous delay here,
        // so we can see both events happening.
        // This has to be done in another thread, otherwise it would
        // freeze the Form (and we don't want that)
        BackgroundWorker delayer = new BackgroundWorker ();

        delayer.DoWork += new DoWorkEventHandler ( OpenDelayer_DoDelay );
        delayer.RunWorkerAsync ();

        #endregion
    }
    else
        return; // aborts if already open
}

private void OpenDelayer_DoDelay ( object sender, DoWorkEventArgs e )
{
    Thread.Sleep ( 3000 );
    _isOpen = true; // sets the door to open state
    RaiseDoorOpened (); // Raise the event after the delay happens
}

As for the Form1 class, the relevant parts are:

The constructor:

public Form1 ()
{
    InitializeComponent ();

    // creates an instance of the Door class
    door = new Door ();

    // event handler for door opening
    door.RaiseDoorOpening += new Door.OpeningDoor(door_OnDoorOpening);

    // event handler for door opened
    door.RaiseDoorOpened += new Door.OpenDoor ( door_OnDoorOpen );

    // event handler for door closing
    door.RaiseDoorClosing += new Door.ClosingDoor(door_OnDoorClosing);

    // event handler for door closed
    door.RaiseDoorClosed += new Door.ClosedDoor ( door_OnDoorClosed );
}

Now the event handlers for opening the door:

private void door_OnDoorOpening ()
{
    string msg = "Opening the door...";
    Console.WriteLine ( msg );
    object [] p = GetInvokerParameters ( msg, Color.Orange );
    BeginInvoke ( new UpdateDoorStatusLabel ( UpdateLabelText ), p );
}

private void door_OnDoorOpen ()
{
    string msg = "Door opened.";
    Console.WriteLine ( msg );
    object [] p = GetInvokerParameters ( msg, Color.Green );
    BeginInvoke ( new UpdateDoorStatusLabel ( UpdateLabelText ), p );
}

Helper method for creating the required parameters for the delegate:

private object [] GetInvokerParameters (string labelMessage,
                                                Color labelColor )
{
    // We have to create an object array as this is the only way our
    // UpdateLabelText method can receive the parameters
    object [] delegateParameter = new object [ 2 ];

    delegateParameter [ 0 ] = labelMessage;
    delegateParameter [ 1 ] = labelColor;

    return delegateParameter;
}

Helper method to update the text and change the forecolor:

private void UpdateLabelText ( string text, Color color )
{
    lblDoorStatus.Text = text;
    lblDoorStatus.ForeColor = color;
}

The Open button Click event handler:

private void btnOpenDoor_Click ( object sender, EventArgs e )
{
    door.Open ();
}

Points of Interest

This code helped me to remember how to use delegates with C# and how to handle events. I also learned how to use the BackgroundWorker for the delay to work properly and the cross-thread call was a bonus (I didn't expect to use it at first when I was coding the event handling).

History

  • 2008-02-09 - 1.0 - First release
    Covers events for Opening, Open, Closing, Close in the Door class.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)

Share

About the Author

Gldmelo
Founder Dice Informatica
Brazil Brazil
Guilherme is bachelor in Computer Science, graduated on 2007 at PUC-RS/Brazil, and post-graduated (latu sensu) on IT Project Management at PUC-RS/Brazil on 2009.
 
Currently working on his own ISV company, he develops custom software solutions for other companies.
 
His carreer interests are:
 
- Software Engineering;
- Computer Graphics;
- Game Development;
- Multi-agent Systems;
- Multi-core programming;
 
He works with C# .net since version 1.1;

Comments and Discussions

 
GeneralThank you Gldmelo Pinmembermaricmwm25-Feb-14 14:18 
Questionthank you! PinmemberMember 351232725-Oct-11 9:11 
QuestionThe best example ever in the world of .NET. PinmemberJohn_G22-Aug-11 3:18 
GeneralThis is not a tutorial Pinmemberoggologgo22-Oct-10 9:57 
AnswerRe: This is not a tutorial PinmemberGldmelo22-Oct-10 10:42 
QuestionBackgroundWorker Class Pinmembervvvlad12-Feb-08 0:30 
GeneralTwo things PinmemberPIEBALDconsult9-Feb-08 13:18 
GeneralSimple and easy to understand Pinmemberthompsons9-Feb-08 8:41 
Simples e bem explicado. Obrigado pelo artigo.
 
Regards,
Steven.
GeneralRe: Simple and easy to understand PinmemberTuiTo9-Feb-08 8:48 

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.141022.2 | Last Updated 8 Feb 2008
Article Copyright 2008 by Gldmelo
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid