开发者

Invoke or BeginInvoke cannot be called on a control until the window handle has been created

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";
         开发者_StackOverflow社区       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;
        }


    }
}


We need more details about where and when the error is happening. My first guess by looking at the code is that you are getting the exception when attempting to close the form. The quitting event handler sets start_a and start_b to false but does not wait until the background threads finish before letting the form run any clean-up code. You now have a race condition between the background thread and the form clean-up. This clean-up code releases the window handle so when the background threads wake up five seconds later and possibly attempts to invoke the text change back to the UI thread you get your failure.

The simplest method to solve the issue is to Join() any live background threads and wait for them to finish before you let the form finish closing. A more correct, and more complex way, would be to setup an appropriate thread synchronization primitive (Mutex, WaitHandle, Sempahore, ...) to allow you to signal the thread to halt immediately.


No easy solution because you have to sync with other threads ending but Invoke ask to execute in the UI thread, the one that is supposed to close the others! So tUI ask t1, t2 to quit, but t1, t2 may need tUI to quit! :)

Adding Application.DoEvents(); (read =process all invoke requests) to quitting method like this:

    private void quitting(object sender, FormClosingEventArgs e)
    {
        start_a = false;
        start_b = false;

        Application.DoEvents(); // NOT the solution, is not enough!!!
    }

Sort most race conditions but is not enough.

Why? Because of this possible, but very improbable, race condition:

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!

Locking the critical section of checking the start variable status and emptying the message queue should do the trick. Your exercise: find other possible race conditions and find a way to exit sooner than 5 seconds in the worst case (hint: don't use sleep. sleep is devil).

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


I think the problem is that you are updating a UI control on a thread other than the thread upon which it was created; I think you should look at this: How to update the GUI from another thread in C#? or here: http://msdn.microsoft.com/en-us/library/system.windows.forms.control.invokerequired.aspx some of the examples are more complicated than need be, but the gist of it is that you have to update the control from the same thread it was created on; Control.InvokeRequired is what you want to pay attention to.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜