2

Usually, when you access controls in a Thread you end up with some cross thread exceptions. In my C# WinForms Application I have a picture box and a toolstriplabel which do not cause that exception. I don't understand why, can anybody explain this to me?

Here some code explanation:

In the main form I have a picturebox and a toolstriplabel. Also I have a reference to another Form, which has no controls and no additional source code. And then in the main form there is another object which works with a thread. This thread can raise three different events and the main form is subscribed to these three events.

  • Event1 causes the toolstriplabel to update (with some information from the thread).
  • Event2 causes the picturebox to update (with a new picture from the thread).

Event1 and Event2 work perfectly fine. I do not use any invoke methods, I directly change Text and BackgroundImage properties without cross thread exception.

  • Event3 though makes troubles. It is supposed to show the other form but I receive the cross therad exception. It works only if I use a BeginInvoke to show the form.

Why is that?

Edit:

The multithreading is done by an MJPEGStream object. I subscribe the NewFrame method of that MJPEGStream object.

public partial class Form1 : Form
{
    private CAM cam;

    private PeekWindow frmPeekWindow;

    public Form1()
    {
        InitializeComponent();

        cam = new CAM();
        cam.NewImageMessageEvent += new NewImageEventHandler(cam_NewImageMessageEvent);
        cam.DetectionEvent += new DetectionEventHandler(cam_DetectionEvent);
        cam.FpsChangedMessageEvent += new FpsChangedEventHandler(cam_FpsChangedMessageEvent);
        cam.DetectionThreshold = (float)this.numDetectionThreshold.Value;

        frmPeekWindow = new PeekWindow();

        // without the next two lines, frmPeekwindow.Show() won't work if called in an event
        frmPeekWindow.Show();
        frmPeekWindow.Hide();
    }

    void cam_FpsChangedMessageEvent(object sender, FpsChangedEventArgs e)
    {
        lblFPS.Text = string.Format("fps: {0:0.0}", e.FPS);
    }

    void cam_DetectionEvent(object sender, DetectionEventArgs e)
    {
        if (chkEnablePeakWindow.Checked)
        {
            if (frmPeekWindow.InvokeRequired)
            {
                frmPeekWindow.Invoke((MethodInvoker)delegate()
                {
                    frmPeekWindow.Show();
                    frmPeekWindow.setImage(e.Image);
                });
            }
            else
            {
                frmPeekWindow.Show();
                frmPeekWindow.setImage(e.Image);
            }
        }
    }

    void cam_NewImageMessageEvent(object sender, NewImageEventArgs e)
    {
        picStream.BackgroundImage = e.Image;
    }
}

And here's the CAM class:

class CAM
{
    private object lockScale = new object();

    private MJPEGStream stream;
    private Bitmap image;

    public event NewImageEventHandler NewImageMessageEvent;
    public event FpsChangedEventHandler FpsChangedMessageEvent;
    public event DetectionEventHandler DetectionEvent;

    // configure (login, pwd, source)
    public CAM()
    {
        this.stream = new MJPEGStream("...");
        this.stream.Login = "...";
        this.stream.Password = "...";
        this.stream.NewFrame += new NewFrameEventHandler(OnNewFrame)
    }

    private void OnNewFrame(object sender, NewFrameEventArgs ev)
    {
        try
        {
            FpsChangedMessageEvent(this, new FpsChangedEventArgs(10));

            // get image
            image = ev.Frame;
            NewImageMessageEvent(this, new NewImageEventArgs(new Bitmap(image)));

            DetectionEvent(this, new DetectionEventArgs(new Bitmap(image)));
        }
        catch (Exception ex)
        {
            Console.Out.WriteLine(ex.Message);
        }
    }
}

3 Answers3

2

You won't get cross thread exception, but it doesn't mean that this is a safe operation. There is always a possibility for your control to go unstable. You just don't know when it will happen.

See the following explanation from Microsoft. http://msdn.microsoft.com/en-us/library/ms171728.aspx

Access to Windows Forms controls is not inherently thread safe. If you have two or more threads manipulating the state of a control, it is possible to force the control into an inconsistent state. Other thread-related bugs are possible, such as race conditions and deadlocks. It is important to make sure that access to your controls is performed in a thread-safe way.

Jason Ching
  • 1,991
  • 1
  • 19
  • 23
  • So the best thing to do would be, using the combination of InvokeRequired/Invoke everywhere, a thread and a control is involved. It is a coincidence that it works here without and I should not rely on that? – user1806262 Jan 29 '13 at 14:21
  • its pretty much coincidence that it worked for you. It all depends on how the event is triggered as to whether it is on the foreground or a background thread and you cannot rely on it being consistent. – Keith Jan 29 '13 at 20:02
0

I have these three possibilites in mind:

  1. The action is already dispatched to the gui thread.
  2. The action doesn't need to be dispatched currently.
  3. The action is somehow executed from the gui thread.

It's most likely number 3.

Andre
  • 1,228
  • 11
  • 24
  • you mean, for example, "picStream.BackgroundImage = e.Image;" is run from the GUI thread for some reason? I checked with "picStream.InvokeRequired", and it returns "True". In other words: it is required (code is running on non-GUI thread) but it doesn't matter that i don't use Invoke. – user1806262 Jan 29 '13 at 14:18
  • In that case it seems the BackgroundImage property simply doesn't cares in every case. But have a look to this similar question: http://stackoverflow.com/questions/6527825/is-it-evil-to-update-a-picturebox-from-a-background-c-sharp-thread – Andre Jan 29 '13 at 14:39
0

You don't necessarily always have to call BeginInvoke/Invoke. Sometimes the operation is running on the foreground thread, sometimes it is in the background.

Per the microsoft samples that are everywhere, You can SHOULD check to see if calling BeginInvoke/Invoke is required.

private void SetTextStandardPattern()
{
    if (this.InvokeRequired)
    {
        this.Invoke(SetTextStandardPattern);
        return;
    }
    this.text = "New Text";
}

Here is a nice microsoft article that has a sample: http://msdn.microsoft.com/en-us/library/ms171728(v=vs.80).aspx

and here is another article on how to "avoid" the pattern: http://www.codeproject.com/Articles/37642/Avoiding-InvokeRequired

Keith
  • 1,119
  • 2
  • 12
  • 23