在窗体句柄创建之前,不能在控件上调用Invoke或BeginInvoke

本文关键字:调用 Invoke BeginInvoke 控件 句柄 窗体 创建 不能 | 更新日期: 2023-09-27 18:01:32

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace try1
{
    public partial class Form1 : Form
    {
        volatile bool start_a = false;
        volatile bool start_b = false;
        public Form1()
        {
            InitializeComponent();
        }
        private void button1_Click(object sender, EventArgs e)
        {
            if (start_a == false)
            {
                button1.Text = "Running";
                start_a = true;
                Thread thread2 = new Thread(new ThreadStart(th1));
                thread2.Start();
            }
            else
            {
                button1.Text = "Click to start";
                start_a = false;
            }
        }
        void th1()
        {
         int a=0;
         while (start_a==true)
         {
             label1.Invoke((MethodInvoker)(() => label1.Text = Convert.ToString(a)));
             Thread.Sleep(50);
             a++;
         }

        }
        void th2()
        {
            int b = 0;
            while (start_b == true)
            {
                label2.Invoke((MethodInvoker)(() => label2.Text = Convert.ToString(b)));
                Thread.Sleep(5000);
                b=b+5;
            }

        }
        private void button2_Click(object sender, EventArgs e)
        {
            if (start_b == false)
            {
                button2.Text = "Running";
                start_b = true;
                Thread thread2 = new Thread(new ThreadStart(th2));
                thread2.Start();
            }
            else
            {
                button2.Text = "Click to start";
                start_b = false;
            }
        }
        private void quitting(object sender, FormClosingEventArgs e)
        {
            start_a = false;
            start_b = false;
        }

    }
}

在窗体句柄创建之前,不能在控件上调用Invoke或BeginInvoke

我们需要更多关于错误发生的地点和时间的详细信息。通过查看代码,我的第一个猜测是,您在试图关闭表单时遇到了异常。退出事件处理程序将start_astart_b设置为false,但不会等到后台线程完成后才让表单运行任何清理代码。现在在后台线程和表单清理之间有了一个竞争条件。这段清理代码释放了窗口句柄,所以当后台线程在5秒后醒来,并可能试图调用文本更改回UI线程时,你就会失败。

解决这个问题的最简单的方法是Join()任何活动的后台线程,并等待他们完成之前,你让表单完成关闭。一种更正确、更复杂的方法是设置一个适当的线程同步原语(Mutex、WaitHandle、sempahor等),以允许您发出信号让线程立即停止。

没有简单的解决方案,因为你必须与其他线程同步,但是调用请求在UI线程中执行,这个线程应该关闭其他线程!所以tUI要求t1 t2退出,但是t1 t2可能需要tUI退出!:)

quitting方法添加Application.DoEvents(); (read =process all invoke requests):

    private void quitting(object sender, FormClosingEventArgs e)
    {
        start_a = false;
        start_b = false;
        Application.DoEvents(); // NOT the solution, is not enough!!!
    }

对大多数竞态条件进行排序,但还不够。

为什么?因为这个可能的,但非常不可能的竞争条件:

t1 before queuing Invoke
                   ~~~~~~~>
                           start_a = false; start_b= false; Application.DoEvents();
                   <~~~~~~~
t1 queue an Invoke
                   ~~~~~~~> (very improbable but possible)
                           (continue trough disposing)
                   <~~~~~~~
queued Invoke on disposed label -> crash!

锁定检查start变量状态和清空消息队列的临界区应该可以解决问题。你的练习:找到其他可能的竞争条件,在最坏的情况下找到一种方法在5秒内退出(提示:不要使用睡眠)。睡眠是魔鬼)。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        object _closing1;
        object _closing2;
        volatile bool start_a = false;
        volatile bool start_b = false;
        public Form1()
        {
            InitializeComponent();
            button1.Text = "Click to start";
            button2.Text = "Click to start";
            _closing1 = new object();
            _closing2 = new object();
        }
        private void button1_Click(object sender, EventArgs e)
        {
            if (start_a == false)
            {
                button1.Text = "Running";
                start_a = true;
                Thread thread2 = new Thread(new ThreadStart(th1));
                thread2.Start();
            }
            else
            {
                button1.Text = "Click to start";
                start_a = false;
            }
        }
        void th1()
        {
            int a = 0;
            while (true)
            {
                lock (_closing1)
                {
                    if (start_a == false)
                        break;
                    label1.BeginInvoke((MethodInvoker)(() => label1.Text = Convert.ToString(a)));
                }
                Thread.Sleep(50);
                a++;
            }
        }
        void th2()
        {
            int b = 0;
            while (true)
            {
                lock (_closing2)
                {
                    if (start_b == false)
                        break;
                    label2.BeginInvoke((MethodInvoker)(() => label2.Text = Convert.ToString(b)));
                }
                Thread.Sleep(5000);
                b = b + 5;
            }
        }
        private void button2_Click(object sender, EventArgs e)
        {
            if (start_b == false)
            {
                button2.Text = "Running";
                start_b = true;
                Thread thread2 = new Thread(new ThreadStart(th2));
                thread2.Start();
            }
            else
            {
                button2.Text = "Click to start";
                start_b = false;
            }
        }
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            lock (_closing1)
            {
                start_a = false;
                // Clear the message queue now so access on disposed lables is possible.
                // No more invokes will be queued because 1) start_a = false
                // 2) t1 is out of the critical section
                Application.DoEvents();
            }
            lock (_closing2)
            {
                start_b = false;
                // Clear the message queue now so access on disposed lables is possible.
                // No more invokes will be queued because 1) start_b = false
                // 2) t2 is out of the critical section
                Application.DoEvents();
            }
        }
    }
}

我认为问题是你正在更新一个线程上的UI控件,而不是在它被创建的线程上;我认为你应该看看这个:如何从c#中的另一个线程更新GUI ?或者在这里:http://msdn.microsoft.com/en-us/library/system.windows.forms.control.invokerequired.aspx一些例子比需要的更复杂,但它的要点是,你必须从同一线程更新控件,它被创建在;控制。