在创建窗口句柄之前,无法在控件上调用Invoke或BeginInvoke

时间:2011-07-09 00:47:20

标签: c# exit invalidoperationexception

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


    }
}

3 个答案:

答案 0 :(得分:1)

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

解决问题的最简单方法是Join()任何实时后台线程并等待它们完成,然后让表单完成关闭。更正确,更复杂的方法是设置适当的线程同步原语(Mutex, WaitHandle, Sempahore, ...),以允许您立即发出线程停止信号。

答案 1 :(得分:0)

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

Application.DoEvents();(read =处理所有调用请求)添加到quitting方法,如下所示:

    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!

锁定检查启动变量状态和清空消息队列的关键部分应该可以解决问题。你的运动:找到其他可能的竞争条件,并在最坏的情况下找到一种方法退出不到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();
            }
        }
    }
}

答案 2 :(得分:-1)

我认为问题在于您正在更新创建它的线程以外的线程上的UI控件;我认为你应该看看这个:How to update the GUI from another thread in C#?或者这里:http://msdn.microsoft.com/en-us/library/system.windows.forms.control.invokerequired.aspx一些例子比需要的更复杂,但它的要点是你必须从同一个线程更新控件创建于; Control.InvokeRequired是你想要注意的。