Возможно построить форму в фоновом потоке, а затем отобразить в потоке пользовательского интерфейса - PullRequest
12 голосов
/ 15 января 2010

ОБНОВЛЕНИЕ: просто, чтобы подвести итог, что мой вопрос сводился к:

Я надеялся, что при создании форм и элементов управления .NET НЕ было создано никаких оконных дескрипторов - надеясь, что процесс будет отложен до Form.Show/Form.ShowDialog

Кто-нибудь может подтвердить или опровергнуть, правда ли это?


У меня есть большая форма WinForms с вкладками, много элементов управления в форме, которая останавливается при загрузке на пару секунд. Я сузил его до сгенерированного дизайнером кода в InitializeComponent, а не какой-либо из моей логики в конструкторе или OnLoad.

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

this.cmbComboBox.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.Suggest;

Я получаю сообщение об ошибке

Текущий поток должен быть установлен на один Поток квартиры (STA) в режиме перед OLE звонки могут быть сделаны. Убедитесь, что ваш Основная функция имеет атрибут STAThreadAttribute отмечено на нем.

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

Есть идеи?

EDIT:

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

Я надеялся, что весь этот код инициализации произошел, фактически не пытаясь прикоснуться к каким-либо реальным объектам окна Win32, так как форма еще не была показана.

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

Ответы [ 6 ]

16 голосов
/ 16 января 2010

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

Windows Forms применяет модель однопоточных квартир. Таким образом, это означает, что в каждом потоке может быть только один цикл сообщений Windows и наоборот. Кроме того, если, например, threadA хочет взаимодействовать с циклом сообщений threadB, он должен перенаправить вызов с помощью таких механизмов, как BeginInvoke.

Однако, если вы создаете новый поток и предоставляете ему свой собственный цикл сообщений, этот поток будет счастливо обрабатывать события независимо, пока ему не будет сказано завершить цикл сообщений.

Для демонстрации ниже приведен код Windows Forms для создания и отображения формы в потоке без графического интерфейса пользователя:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        label1.Text = Thread.CurrentThread.ManagedThreadId.ToString();

    }

    private void button1_Click(object sender, EventArgs e)
    {
        ThreadStart ts = new ThreadStart(OpenForm);

        Thread t = new Thread(ts);
        t.IsBackground=false;

        t.Start(); 
    }

    private void OpenForm()
    {
        Form2 f2 = new Form2();

        f2.ShowDialog();
    }
}


public partial class Form2 : Form
{
    public Form2()
    {
        InitializeComponent();
    }

    private void Form2_Load(object sender, EventArgs e)
    {
        label1.Text = Thread.CurrentThread.ManagedThreadId.ToString() ;

    }
}

Метод OpenForm запускается в новом потоке и создает экземпляр Form2.

Form2 фактически получает свой собственный отдельный цикл сообщений, вызывая ShowDialog (). Если бы вместо этого вы вызывали Show (), цикл сообщений не предоставлялся бы, и Form2 немедленно закрывался бы.

Кроме того, если вы попытаетесь получить доступ к Form1 в OpenForm () (например, с помощью 'this'), вы получите сообщение об ошибке во время выполнения при попытке доступа к пользовательскому интерфейсу между потоками.

t.IsBackground=false устанавливает нить как нить переднего плана. Нам нужен поток переднего плана, потому что фоновые потоки уничтожаются немедленно, когда основная форма закрывается без предварительного вызова событий FormClosing или FormClosed.

Помимо этих пунктов, Form2 теперь может использоваться как любая другая форма. Вы заметите, что Form1 все еще счастливо работает как обычно с собственным сообщением lopp. Это означает, что вы можете нажать на кнопку и создать несколько экземпляров Form2, каждый из которых имеет свой отдельный цикл и поток сообщений.

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

4 голосов
/ 15 января 2010

Я думаю, что ваше понимание немного не в порядке. К элементам управления следует обращаться из потока, который их создал, а не из основного потока пользовательского интерфейса. В приложении может быть множество потоков пользовательского интерфейса, каждый со своим собственным набором элементов управления. Таким образом, создание элемента управления в другом потоке не позволит вам работать с ним из основного потока без маршалинга всех вызовов поверх Invoke или BeginInvoke.

EDIT Некоторые ссылки на несколько потоков пользовательского интерфейса:

MSDN в циклах сообщений MSDN социальная дискуссия Несколько потоков в WPF

3 голосов
/ 15 января 2010

Ответ - нет.

Если вы создаете дескриптор окна в любом потоке, кроме потока GUI, вы никогда не сможете его показать.

Редактировать: вполне возможно создавать формы и элементы управления и отображать их в потоке, отличном от основного потока графического интерфейса. Конечно, если Вы делаете это, вы можете получить доступ только к многопоточному GUI из потока что создал это, но это возможно. - Эшли Хендерсон

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

1 голос
/ 19 ноября 2011

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

Таким образом, есть 2 потока, 'NewCompThread' и 'MainThread'.

Вы раскручиваете NewCompThread, и он создает компоненты для вас - все готово для отображения в MainUI (создан в MainThread).

Но ... вы получите исключение, если попробуете что-то подобное в NewCompThread: ComponentCreatedOnNewCompTHread.parent = ComponentCreatedOnMainThread;

Но вы можете добавить это:

if (ComponentCreatedOnMainThread.InvokeRequired) {
  ComponentCreatedOnMainThread.Invoke(appropriate delegate...);
} else {
  ComponentCreatedOnNewCompTHread.parent = ComponentCreatedOnMainThread; 
}

И это будет работать. Я сделал это.
Странная вещь (для меня) заключается в том, что тогда ComponentCreatedOnNewCompTHread «думает», что он был создан на MainThread.

Если вы делаете следующее из NewCompThread: ComponentCreatedOnNewCompTHread.InvokeRequired он вернет TRUE, и вам нужно будет создать делегат и использовать Invoke, чтобы вернуться к MainThread.

1 голос
/ 15 января 2010

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

Даже если вы заставите это работать, что это купит вас? В целом это будет немного медленнее, а не быстрее.

Возможно, просто показать заставку во время загрузки этой формы?

В качестве альтернативы рассмотрите, почему ваша форма создается так долго. Обычно это занимает несколько секунд.

0 голосов
/ 10 августа 2017

Создание элемента управления в фоновом потоке возможно, но только в потоке STA.

Я создал метод расширения, чтобы использовать его с шаблоном async / await

private async void treeview1_AfterSelect(object sender, TreeViewEventArgs e)
{
    var control = await CreateControlAsync(e.Node);
    if (e.Node.Equals(treeview1.SelectedNode)
    {
        panel1.Controls.Clear();
        panel1.Controls.Add(control);
    }
    else
    {
        control.Dispose();
    }
}

private async Control CreateControlAsync(TreeNode node)
{
    return await Task.Factory.StartNew(() => CreateControl(node), ApartmentState.STA);
}

private Control CreateControl(TreeNode node)
{
    // return some control which takes some time to create
}

Это метод расширения. Задача не позволяет установить квартиру, поэтому я использую нить внутри.

public static Task<T> StartNew<T>(this TaskFactory t, Func<T> func, ApartmentState state)
{
    var tcs = new TaskCompletionSource<T>();
    var thread = new Thread(() =>
    {
        try
        {
            tcs.SetResult(func());
        }
        catch (Exception e)
        {
            tcs.SetException(e);
        }
    });
    thread.IsBackground = true;
    thread.SetApartmentState(state);
    thread.Start();
    return tcs.Task;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...