Invoke или BeginInvoke не могут быть вызваны для элемента управления, пока не будет создан дескриптор окна - PullRequest
1 голос
/ 09 июля 2011
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 ]

1 голос
/ 09 июля 2011

Нам нужны более подробные сведения о том, где и когда произошла ошибка.Мое первое предположение, глядя на код, состоит в том, что вы получаете исключение при попытке закрыть форму.Обработчик события выхода устанавливает для start_a и start_b значение false, но не ожидает завершения фоновых потоков, прежде чем позволить форме выполнить любой код очистки.Теперь у вас есть условие гонки между фоновым потоком и очисткой формы.Этот код очистки освобождает дескриптор окна, поэтому, когда фоновые потоки просыпаются через пять секунд и, возможно, пытаются вызвать изменение текста обратно в поток пользовательского интерфейса, вы получаете ошибку.

Самый простой способ решения проблемы Join () любых живых фоновых потоков и дождитесь их завершения, прежде чем завершить закрытие формы.Более правильным и более сложным способом было бы установить соответствующий примитив синхронизации потока ( Mutex, WaitHandle, Sempahore, ... ), чтобы дать вам сигнал немедленно остановить поток.

0 голосов
/ 09 июля 2011

Не простое решение, потому что вам нужно синхронизировать с завершением других потоков, но Invoke просит выполнить в потоке пользовательского интерфейса, который должен закрыть другие! Таким образом, tII просит t1, t2 выйти, но t1, t2 может потребоваться tUI для выхода! :)

Добавление Application.DoEvents(); (чтение = обработка всех запросов) в метод 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();
            }
        }
    }
}
0 голосов
/ 09 июля 2011

Мне кажется, проблема в том, что вы обновляете элемент управления пользовательского интерфейса в потоке, отличном от потока, в котором он был создан; Я думаю, вы должны посмотреть на это: Как обновить графический интерфейс из другого потока в C #? или здесь: http://msdn.microsoft.com/en-us/library/system.windows.forms.control.invokerequired.aspx некоторые примеры более сложны, чем нужно, но суть Вы должны обновить элемент управления из того же потока, в котором он был создан; Control.InvokeRequired - это то, на что вы хотите обратить внимание.

...