Как открыть форму в ветке и заставить ее оставаться открытой

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

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

using System;
using System.Windows.Forms;
using System.Threading;

namespace UIThreadMarshalling {
    static class Program {
        [STAThread]
        static void Main() {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            var tt = new ThreadTest();
            ThreadStart ts = new ThreadStart(tt.StartUiThread);
            Thread t = new Thread(ts);
            t.Name = "UI Thread";
            t.Start();
            Thread.Sleep(new TimeSpan(0, 0, 10));
        }

    }

    public class ThreadTest {
        Form _form;
        public ThreadTest() {
        }

        public void StartUiThread() {
            _form = new Form1();
            _form.Show();
        }
    }
}

6 ответов

Решение

В новом потоке вызовите Application.Run, передав объект формы, это заставит поток запустить свой собственный цикл обработки сообщений, пока окно открыто.

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

Пример:

public void StartUiThread()
{
    using (Form1 _form = new Form1())
    {
        Application.Run(_form);
    }
}
private void button1_Click(object sender, EventArgs e)
{
    var t = new Thread(RunNewForm);
    t.Start();
}
public static void RunNewForm()
{
     Application.Run(new Form2());
}

Я думаю, что ваша проблема с этой мыслью: "открыть форму в отдельном потоке с именем" поток пользовательского интерфейса ""

Windows работает следующим образом (обратите внимание, Vista может изменить некоторые из этих реалий):

Существует один важный поток, который называется "Основной поток" или "Поток пользовательского интерфейса". Этот поток обрабатывает сообщения Windows, например, "эй, щелкнул мышкой по этому пикселю".

Эти сообщения попадают в очередь, и главный поток обрабатывает их, когда он не занят чем-то другим.

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

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

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

Эта реальность вызывает две проблемы:

  1. ВЫХОД ИЗ ГЛАВНОЙ РЕЗЬБЫ: Поскольку вы не хотите, чтобы долго выполняющаяся функция foo() прекращала все взаимодействие с пользователем, вам необходимо перенести эту работу в рабочий поток.

  2. ПОЛУЧЕНИЕ ВЕРНУТЬСЯ В ГЛАВНУЮ РЕЗЬБУ: Когда завершается долго выполняющаяся функция foo(), вы, вероятно, захотите уведомить пользователя, делая что-то в пользовательском интерфейсе, но вы не можете сделать это в рабочем потоке, поэтому вам нужно "вернуться" к Основная тема.

Поэтому я считаю, что ваша проблема в вышеприведенной программе очень общая: ваша цель неверна, поскольку предполагается, что нельзя вызывать _form.Show() в каком-либо потоке, кроме основного основного потока.

Вы не можете открыть форму GUI в каком-либо потоке, потому что в ней будет отсутствовать насос сообщений. Вы должны явно запустить насос сообщений в этом потоке, вызвав Application.Run() в методе потока. Другой вариант - вызвать DoEvents() в цикле, если вам нужно сделать что-то еще, потому что после Application.Run() этот поток будет ждать, когда пользователь закроет форму в этой точке выполнения.

Вместо вызова show() в форме, которая будет выполняться в форме и затем просто закрываться в конце выполнения потока внутри функции StartUiThread(), вы можете заблокировать поток до тех пор, пока форма не будет остановлена ​​внутри метода, поскольку вы просто блокируете другая нить. Пример:

public void StartUiThread() {
        _form = new Form1();
        _form.ShowDialog(); //Change Show() to ShowDialog() to wait in thread
    }

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

Я думаю, что просто вызов ShowDialog вместо Show поможет. Похоже, проблема заключается в том, что поток завершает работу сразу после вызова Show, после чего сборщик мусора формы get. ShowDialog остановит поток, но все равно запустит в нем события формы, поэтому поток продолжит работать, пока форма не будет закрыта.

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

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

Другие вопросы по тегам