首页 文章

为什么我可以从线程中的表单访问控件而没有跨线程异常?

提问于
浏览
0

通常,当您访问线程中的控件时,最终会出现一些跨线程异常 . 在我的C#WinForms应用程序中,我有一个图片框和一个工具条标签,它们不会导致异常 . 我不明白为什么,有人可以向我解释一下吗?

这里有一些代码说明:

在主要形式中,我有一个picturebox和一个toolstriplabel . 此外,我还引用了另一个表单,该表单没有控件,也没有其他源代码 . 然后在主窗体中有另一个与线程一起工作的对象 . 该线程可以引发三个不同的事件,主表单订阅这三个事件 .

  • Event1导致toolstriplabel更新(带有来自线程的一些信息) .

  • Event2导致图片框更新(使用线程中的新图片) .

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

  • Event3虽然有麻烦 . 它应该显示另一种形式,但我收到了交叉的therad例外 . 它只有在我使用BeginInvoke来显示表单时才有效 .

这是为什么?

Edit:

多线程由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);
        }
    }
}

3 回答

  • 1

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

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

    访问Windows窗体控件本身并不是线程安全的 . 如果有两个或多个线程操纵控件的状态,则可以强制控件进入不一致状态 . 其他与线程相关的错误也是可能的,例如竞争条件和死锁 . 确保以线程安全的方式访问控件非常重要 .

  • 0

    我有三个可能性:

    • 该动作已经发送到gui线程 .

    • 当前不需要调度该操作 .

    • 动作以某种方式从gui线程执行 .

    这很可能是3号 .

  • 0

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

    根据无处不在的微软样本,您可以检查是否需要调用BeginInvoke / Invoke .

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

    这是一篇很好的微软文章,有一个样本:http://msdn.microsoft.com/en-us/library/ms171728(v=vs.80).aspx

    这是另一篇关于如何"avoid"模式的文章:http://www.codeproject.com/Articles/37642/Avoiding-InvokeRequired

相关问题