tags:

views:

156

answers:

2

I've got some backgroundworker threads that are working on calculations or saving data. Every time a thread is done working I'm first adding a message to a static messagelist and then I'm showing this in a richtextbox.

The message contains the hour, a label, the message and a message type. This message type I'm using to show the message in a specific color.

Now I've got the problem that some times a 2 threads are done at the same time and try to set a message. So I got the cross-thread exception.

I know that I can solve this by using a delegate. But I'm kinda stuck here.

this is how I set my message currently:

private void SetMessages()
{
   rtxtMessage.Text = "";

   foreach (var message in GlobalVariables.MessageList)
   {
      var text = message.Date.ToShortTimeString() + " " + message.Label + ": " + 
                 message.TheMessage;

      switch (message.Type)
      {
         case GlobalVariables.MessageType.normal:
             rtxtMessage.SelectionColor = Color.Black;
             break;
         case GlobalVariables.MessageType.calculation:
             rtxtMessage.SelectionColor = Color.Green;
             break;
         case GlobalVariables.MessageType.error:
             rtxtMessage.SelectionColor = Color.Red;
             break;
         case GlobalVariables.MessageType.warning:
             rtxtMessage.SelectionColor = Color.Orange;
             break;
         default:
             break;
      }
      rtxtMessage.SelectedText = text + Environment.NewLine;
      rtxtMessage.ScrollToCaret();
   }
   pnlMessage.Visible = true;
}

So main question is how can I rewrite this to get it working with a delegate?

+2  A: 

I interpret your question as if you are using the BackgroundWorker class for the threaded work. Then then question is how the messages come into the GlobalVariables.MessageList collection. If it is done in the RunWorkerCompleted event, you should not have any cross-threading problems, since it executes on the UI thread (this is the way I would recommend you to do). If it is updated directly from the worker process you will need to take care of synchronization and threading issues yourself (using one of the available locking mechanisms).

Update (after the comment response in the original question):

The RunWorkerCompleted should typically run on the UI thread (or perhaps rather on the thread on which RunWorkerAsync was invoked, I guess), so you should typically not need to bother about threading issues when updating the UI from that thread. However, to be really sure, you can use the following approach:

private void BackgroundWorker_RunWorkerCompleted(object sender, 
                           System.ComponentModel.RunWorkerCompletedEventArgs e)
{
    if (this.InvokeRequired)
    {
        this.Invoke(
            new Action<object, RunWorkerCompletedEventArgs>(
                               BackgroundWorker_RunWorkerCompleted), sender, e);
    }
    else
    {
        // update the message list, and then call SetMessages()
        SetMessages();
    }
}

That said, I would urge you to pinpoint under exactly which circumstances the threading exception occurs, since it should really not happen using the RunWorkerCompleted event. I ran a test where I started a large (a couple of hundred) number of backgroundworkers simultaneously, and failed to provoke any sort of collision. It also did not use the Invoke-path in the above code sample once.

As a side note I think that you would not need to first store the messages in a list, just to clear and re-populate the textbox with all messages each time. I think you could instead change the SetMessages method into a method that takes a message as paramter and that simply adds the message to the textbox:

private void SetMessage(MyMessage message)
{
  var text = message.Date.ToShortTimeString() + " " + message.Label + ": " +             message.TheMessage;

  switch (message.Type)
  {
     case GlobalVariables.MessageType.normal:
         rtxtMessage.SelectionColor = Color.Black;
         break;
     case GlobalVariables.MessageType.calculation:
         rtxtMessage.SelectionColor = Color.Green;
         break;
     case GlobalVariables.MessageType.error:
         rtxtMessage.SelectionColor = Color.Red;
         break;
     case GlobalVariables.MessageType.warning:
         rtxtMessage.SelectionColor = Color.Orange;
         break;
     default:
         break;
  }
  rtxtMessage.SelectedText = text + Environment.NewLine;
  rtxtMessage.ScrollToCaret();

  pnlMessage.Visible = true;
}

You should be able to call this method straight from the RunWorkerCompleted event handler and just pass the message into the method.

Fredrik Mörk
it is added in the runworkercompleted event
Gerbrand
in the runworkercompleted I'm adding my message to the list and then i'm calling the setmessages() function.
Gerbrand
I interpreted it as a background worker thread. Now this is confirmed I've deleted my answer as it is no longer relevant.
RichardOD
I'll add your solution to my app. On the messages, I need to store them in my list because another view is reading also these messages
Gerbrand
A: 

You should be able to get this working by locking your static Message List like:

private void SetMessages()
{
   rtxtMessage.Text = "";
   lock(GlobalVariables.MessageList)
   {
      foreach (var message in GlobalVariables.MessageList)   
      { 
         //Rest of your code 
      }   
      pnlMessage.Visible = true;
   }
}
John Hunter