简体   繁体   中英

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?

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

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.

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...
    }
}

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();
   });

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM