Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

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

0.00/5 (No votes)
8 Feb 2008 1  
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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here