Создание элементов управления в потоке без пользовательского интерфейса - PullRequest
7 голосов
/ 25 июня 2009

У меня есть своего рода модель подключаемого модуля, в которой различные сложные пользовательские элементы управления хранятся в DLL-библиотеках, загружаются и создаются во время выполнения с использованием

Activator.CreateInstanceFrom(dllpath, classname).

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

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

Итак, мой вопрос, есть ли какой-нибудь безопасный способ сделать то, что я пытаюсь, или как мне лучше всего загружать эти элементы управления в фоновом режиме? Или это в принципе невозможно, и мне нужно придерживаться основного потока для создания элементов управления?

Я надеюсь, что предоставленной мной информации достаточно, чтобы прояснить мою ситуацию; в противном случае я был бы рад разработать и предоставить примеры кода.

Ответы [ 3 ]

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

Можно загружать библиотеки DLL и создавать объекты элементов управления в фоновом режиме, но элемент управления должен быть добавлен в форму в основном потоке, а также при любом взаимодействии с пользователем, а также при любом программном изменении свойств элемента управления (после его создания). ) должно происходить в основном потоке. Как вы, наверное, уже знаете, просто невозможно обойти это. Теперь, если загрузка этих DLL и создание элементов управления не занимает много времени, я не вижу причин делать это в отдельных фоновых потоках.

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

Попытка создать универсальную универсальную инфраструктуру, которая работает одинаково в режиме потока пользовательского интерфейса и в фоновом режиме и просто проверяет результаты InvokeRequired иногда при худшей производительности (поскольку все потоки блокируются в Вызывать) или даже в (необнаруженных) взаимоблокировках, как только приложение достигает разумной сложности. Также выполнение всех асинхронных обновлений в BeginInvoke, без учета каждого метода в отдельности, может привести к проблемам согласованности данных (т. Е. Элементы управления могут обновляться до состояний назад во времени из-за изменения порядка вызовов между потоками) .

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

Не зная подробностей механизма InvokeRequired, я немного поэкспериментировал, и на деле вы можете установить большинство свойств в потоке, если он не был Parented (т.е. добавлен в какое-либо свойство Control.Controls).

Таким образом, вы сможете подготовить свои элементы управления, сохранить их в списке и прикрепить их к основному пользовательскому интерфейсу с помощью метода Invoked.


Edit: я не думаю, что имеет значение, какой поток создал элемент управления. Поэтому должны применяться нормальные правила, т.е. вы можете работать только с элементами управления из основного потока Windows. И я думаю, что критерием является HandleCreated, а не Parented. Пока этого не произошло, вы получаете небольшую передышку.

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

Пример кода в этот ответ предоставляет элегантный способ обойти эту проблему.

Код цитируется из этого ответа:

public void UpdateTestBox(string newText)
{
    BeginInvoke((MethodInvoker) delegate {
        tb_output.Text = newText;
    });        
}

... хотя в вашем случае вы захотите вызвать BeginInvoke для самих элементов управления:

public void UpdateTestBox(string newText)
{
    tb_output.BeginInvoke((MethodInvoker) delegate {
        tb_output.Text = newText;
    });        
}
...