Исключение «операция между потоками недопустима» для внутренних элементов управления - PullRequest
7 голосов
/ 10 июня 2009

Я долго боролся с этим: У меня есть функция, предназначенная для добавления элемента управления на панель с межпотоковой обработкой, проблема в том, что хотя панель и элемент управления находятся в «InvokeRequired = false» - я получаю исключение, сообщающее мне, что к одному из внутренних элементов управления элементов управления обращаются из потока, отличного от потока, в котором он был создан, фрагмент выглядит следующим образом:

public delegate void AddControlToPanelDlgt(Panel panel, Control ctrl);
    public void AddControlToPanel(Panel panel, Control ctrl)
    {
        if (panel.InvokeRequired)
        {
            panel.Invoke(new AddControlToPanelDlgt(AddControlToPanel),panel,ctrl);
            return;
        }
        if (ctrl.InvokeRequired)
        {
            ctrl.Invoke(new AddControlToPanelDlgt(AddControlToPanel),panel,ctrl);
            return;
        }
        panel.Controls.Add(ctrl); //<-- here is where the exception is raised
    }

сообщение об исключении выглядит следующим образом:

"Операция с несколькими потоками недопустима: элемент управления 'pnlFoo' доступен из потока, отличного от потока, в котором он был создан"

(pnlFoo находится под управлением ctrl.Controls)

Как мне добавить ctrl на панель?!


Когда код достигает «panel.Controls.Add (ctrl);» line - для свойства InvokeRequired панели и ctrl задано значение false, проблема заключается в том, что для элементов управления внутри ctrl значение InvokeRequired установлено в значение true. Чтобы прояснить ситуацию: «панель» создается в базовом потоке, а «ctrl» - в новом потоке, поэтому необходимо вызывать «панель» (что вызывает необходимость повторного вызова «ctrl»). Как только оба вызова завершены, он достигает команды panel.Controls.Add (ctrl) (в этом состоянии не нужно вызывать и "panel", и "ctrl")

Вот небольшой фрагмент полной программы:

public class ucFoo : UserControl
{
    private Panel pnlFoo = new Panel();

    public ucFoo()
    {
        this.Controls.Add(pnlFoo);
    }
}

public class ucFoo2 : UserControl
{
    private Panel pnlFooContainer = new Panel();

    public ucFoo2()
    {
         this.Controls.Add(pnlFooContainer);
         Thread t = new Thread(new ThreadStart(AddFooControlToFooConatiner());
         t.Start()
    }

    private AddFooControlToFooConatiner()
    {
         ucFoo foo = new ucFoo();
         this.pnlFooContainer.Controls.Add(ucFoo); //<-- this is where the exception is raised
    }
}

Ответы [ 7 ]

3 голосов
/ 10 июня 2009

'panel' и 'ctrl' должны быть созданы в одном потоке, т.е. Вы не можете сделать так, чтобы panel.InvokeRequired возвращало значение, отличное от ctrl.InvokeRequired. То есть , если и на панели, и в ctrl созданы дескрипторы или они принадлежат контейнеру с созданным дескриптором . От MSDN :

Если ручка элемента управления еще не существует, InvokeRequired ищет вверх родительская цепочка элемента управления, пока не найдет элемент управления или форма, которая имеет оконная ручка Если не подходит ручку можно найти, то Метод InvokeRequired возвращает значение false.

Как сейчас, ваш код открыт для условий гонки , поскольку panel.InvokeNeeded может возвращать false, поскольку панель еще не создана, тогда ctrl.InvokeNeeded обязательно вернет false, поскольку наиболее вероятно, что ctrl еще не добавлен ни в один контейнер, а затем к моменту достижения panel.Controls.Add панель была создана в главном потоке, поэтому вызов не будет выполнен.

3 голосов
/ 10 июня 2009

В качестве отступления - чтобы избавить себя от необходимости создавать бесчисленные типы делегатов:

if (panel.InvokeRequired)
{
    panel.Invoke((MethodInvoker) delegate { AddControlToPanel(panel,ctrl); } );
    return;
}

Кроме того, теперь выполняются регулярные статические проверки внутреннего вызова AddControlToPanel, поэтому вы не можете ошибиться.

3 голосов
/ 10 июня 2009

Где создается pnlFoo и в каком потоке? Вы знаете, когда создается его ручка? Если он создается в исходном (не-пользовательском) потоке, это проблема.

Все элементы управления в одном и том же окне должны быть созданы и доступны в одном потоке. На этом этапе вам не нужно две проверки того, требуется ли Invoke, потому что ctrl и panel должны использовать один и тот же поток.

Если это не поможет, предоставьте короткую, но полную программу для демонстрации проблемы.

1 голос
/ 28 декабря 2009

Здесь много интересных ответов, но одним из ключевых элементов любой многопоточности в приложении Winform является использование BackgroundWorker для инициирования потоков и связи с основным потоком Winform.

1 голос
/ 10 июня 2009

Вот рабочий фрагмент кода:

public delegate void AddControlToPanelDlg(Panel p, Control c);

        private void AddControlToPanel(Panel p, Control c)
        {
            p.Controls.Add(c);
        }

        private void AddNewContol(object state)
        {
            object[] param = (object[])state;
            Panel p = (Panel)param[0];
            Control c = (Control)param[1]
            if (p.InvokeRequired)
            {
                p.Invoke(new AddControlToPanelDlg(AddControlToPanel), p, c);
            }
            else
            {
                AddControlToPanel(p, c);
            }
        }

А вот как я это проверял. Вам нужно иметь форму с 2 кнопками и одной flowLayoutPanel (я выбрал это, поэтому мне не нужно было заботиться о местоположении при динамическом добавлении элементов управления на панели)

private void button1_Click(object sender, EventArgs e)
        {
            AddNewContol(new object[]{flowLayoutPanel1, CreateButton(DateTime.Now.Ticks.ToString())});
        }

        private void button2_Click(object sender, EventArgs e)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(AddNewContol), new object[] { flowLayoutPanel1, CreateButton(DateTime.Now.Ticks.ToString()) });
        }

Мне кажется, что ваш пример состоит в том, что когда вы входите в ветку InvokeRequired, вы вызываете ту же функцию, в которой вы находитесь, что приводит к странному случаю повторения.

1 голос
/ 10 июня 2009

В своем ответе вы утверждаете:

Чтобы прояснить ситуацию: "панель" создается в основном потоке, а "ctrl" - в новом потоке

Я думаю, что это может быть причиной вашей проблемы. Все элементы пользовательского интерфейса должны быть созданы в одном потоке (базовом). Если вам нужно создать «ctrl» как следствие какого-либо действия в новом потоке, то запустите событие обратно в базовый поток и выполните его создание.

0 голосов
/ 10 июня 2009

Вот небольшой фрагмент полной программы:

public class ucFoo : UserControl
{
    private Panel pnlFoo = new Panel();

    public ucFoo()
    {
        this.Controls.Add(pnlFoo);
    }
}

public class ucFoo2 : UserControl
{
    private Panel pnlFooContainer = new Panel();

    public ucFoo2()
    {
         this.Controls.Add(pnlFooContainer);
         Thread t = new Thread(new ThreadStart(AddFooControlToFooConatiner());
         t.Start()
    }

    private AddFooControlToFooConatiner()
    {
         ucFoo foo = new ucFoo();
         this.pnlFooContainer.Controls.Add(ucFoo); //<-- this is where the exception is raised
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...