1

通常,当您访问线程中的控件时,您最终会遇到一些跨线程异常。在我的 C# WinForms 应用程序中,我有一个图片框和一个工具条标签,它们不会导致该异常。我不明白为什么,谁能给我解释一下?

这里有一些代码解释:

在主窗体中,我有一个图片框和一个工具条标签。此外,我还引用了另一个表单,它没有控件,也没有额外的源代码。然后在主窗体中有另一个与线程一起工作的对象。该线程可以引发三个不同的事件,主窗体订阅这三个事件。

  • Event1 导致工具条标签更新(带有来自线程的一些信息)。
  • Event2 导致图片框更新(使用来自线程的新图片)。

Event1 和 Event2 工作得很好。我不使用任何调用方法,我直接更改 Text 和 BackgroundImage 属性而没有跨线程异常。

  • Event3虽然会造成麻烦。它应该显示另一种形式,但我收到了交叉治疗异常。只有当我使用 BeginInvoke 来显示表单时它才有效。

这是为什么?

编辑:

多线程由MJPEGStream对象完成。我订阅了该 MJPEGStream 对象的NewFrame方法。

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

这是 CAM 类:

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

3 回答 3

2

你不会得到跨线程异常,但这并不意味着这是一个安全的操作。您的控制总是有可能变得不稳定。你只是不知道它什么时候会发生。

请参阅 Microsoft 的以下说明。 http://msdn.microsoft.com/en-us/library/ms171728.aspx

对 Windows 窗体控件的访问本质上不是线程安全的。如果您有两个或更多线程操作控件的状态,则可能会强制控件进入不一致的状态。其他与线程相关的错误是可能的,例如竞争条件和死锁。确保以线程安全的方式执行对控件的访问非常重要。

于 2013-01-29T11:27:19.867 回答
0

我想到了这三种可能性:

  1. 该操作已被分派到 gui 线程。
  2. 当前不需要分派该操作。
  3. 该操作以某种方式从 gui 线程执行。

最有可能是3号。

于 2013-01-29T11:14:33.013 回答
0

您不必总是调用 BeginInvoke/Invoke。有时操作在前台线程上运行,有时在后台运行。

根据无处不在的 microsoft 示例,您可以检查是否需要调用 BeginInvoke/Invoke。

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

这是一篇不错的 Microsoft 文章,其中包含示例: http: //msdn.microsoft.com/en-us/library/ms171728 (v=vs.80).aspx

这是另一篇关于如何“避免”该模式的文章:http: //www.codeproject.com/Articles/37642/Avoiding-InvokeRequired

于 2013-01-29T19:59:20.453 回答