Подтвердить что ты не робот

Можно создать форму в фоновом потоке, затем отобразить в потоке пользовательского интерфейса

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

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

Может ли кто-нибудь подтвердить или опровергнуть, верно ли это?


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

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

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

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

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

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

Любые идеи?

EDIT:

Думаю, я не объясню здесь. Задержка, по-видимому, имеет место при создании элементов управления bazillion во время кода, созданного конструктором.

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

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

4b9b3361

Ответ 1

Ответ - нет.

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

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

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

Ответ 2

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

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

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

Итак, чтобы продемонстрировать, ниже приведен код Windows Forms для создания и отображения формы в потоке без GUI:

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.

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

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

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

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

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

Ответ 3

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

ИЗМЕНИТЬ Некоторые ссылки для нескольких потоков пользовательского интерфейса:

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

Ответ 4

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

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

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

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

Ответ 5

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

Итак, есть 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.

Ответ 6

Создание элемента управления в фоновом потоке возможно, но только в потоке 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;
}