fishhead
fishhead

Reputation: 6109

what is the best way to handle potential non thread safe events

please consider the following scenario for .net 2.0:

I have an event that is fired on system.Timers.Timer object. The subscriber then adds an item to a Windows.Forms.Listbox upon receiving the event. This results in a cross-thread exception.

My question is what would be the best way to handle this sort of situation. The solutions that I have come up with is as follows:

private   delegate void messageDel(string text);
private   void ThreadSafeMsg(string text)
{
  if (this.InvokeRequired)
  {
    messageDel d = new messageDel(ThreadSafeMsg);
    this.Invoke(d, new object[] { text });
  }
  else
  {
  listBox1.Items.Add(text);
  listBox1.Update();
  }
}

 // event
void Instance_Message(string text)
{
  ThreadSafeMsg(text);
}

Is this the optimum way to handle this in .net 2? What about .net 3.5?

Upvotes: 0

Views: 172

Answers (5)

supercat
supercat

Reputation: 81159

Depending upon what your action is doing, Control.BeginInvoke may be better than Control.Invoke. Control.Invoke will wait for the UI thread to process your message before it returns. If the UI thread is blocked, it will wait forever. Control.BeginInvoke will enqueue a message for the UI thread and return immediately. Because there's no way to avoid an exception if a control gets disposed immediately before you try to BeginInvoke it, you need to catch (possibly swallow) the exception (I think it may be either ObjectDisposedException or IllegalOperationException depending upon timing). You also need to set a flag or counter when you're about to post a message and clear or decrement it in the message handler (probably use Threading.Interlocked.Increment/Decrement), to ensure that you don't enqueue an excessive number of messages while the UI thread is blocked.

Upvotes: 0

Sergey Teplyakov
Sergey Teplyakov

Reputation: 11647

The easiest solution in your case - is using System.Windows.Forms.Timer class, but in general case you may use following solution to access you GUI-stuff from non-GUI thread (this solution applicable for .net 2.0 but it more elegant for .net 3.5):

public static class ControlExtentions
{
    public static void InvokeIfNeeded(this Control control, Action doit)
    {
        if (control.InvokeRequired)
            control.Invoke(doit);
        else
            doit();
    }
}

And you may use it like this no mater from what thread, from UI or from another one:

this.InvokeIfNeeded(()=>
   {
    listBox1.Items.Add(text);
    listBox1.Update();
   });

Upvotes: 0

Kel
Kel

Reputation: 1235

It is pretty much the same in .net 3.5, since it is related to Windows Forms and cross-threading when you are accessing the UI thread from some another working thread. However, you can make the code smaller, by using the generic Action<> and Func<>, avoiding creating manually the delegates.

Something like this:

private void ThreadSafeMsg(string text)
{
    if (this.InvokeRequired)
        this.Invoke(new Action<string>(ThreadSafeMsg), new object[] { text });
    else
    {
        // Stuff...
    }
}

Upvotes: 1

Hans Passant
Hans Passant

Reputation: 941485

There's no point in using Control.InvokeRequired, you know that it always is. The Elapsed event is raised on a threadpool thread, never the UI thread.

Which makes it kinda pointless to use a System.Timers.Timer, just use the System.Windows.Forms.Timer. No need to monkey with Control.Begin/Invoke, you can't crash your program with an ObjectDisposedException when the event is raised just as the user closes the form.

Upvotes: 1

user195488
user195488

Reputation:

You have a cross thread exception because you are trying to access items from outside the UI thread. Delegates are necessary in order to hook into the message pump and make the UI change.

If you use the Form Timer, then you'll be in the UI thread. You'll have the same problem, however, if you use a BackgroundWorkerThread and you'll need a delegate there as well.

See Threading in Windows Forms

Upvotes: 1

Related Questions