Как обновить текстовое поле в новом окне, открытом новым потоком?

#c# #wpf #multithreading

#c# #wpf #многопоточность

Вопрос:

Я могу открыть новое окно в новом потоке с помощью следующего кода.

Следующий код взят из MainWindow.xaml.cs

 private void buttonStartStop_Click(object sender, RoutedEventArgs e)
{    

  Test test = new Test();

  Thread newWindowThread = new Thread(new ThreadStart(test.start));
  newWindowThread.SetApartmentState(ApartmentState.STA);
  newWindowThread.IsBackground = true;
  newWindowThread.Start();
}
  

и следующее из test.start()

 public void start()
{

  OutputWindow outputwindow = new OutputWindow();
  outputwindow.Show();


  Output.print("Begin");
  System.Windows.Threading.Dispatcher.Run();
  Output.print("FINAL");
  System.Windows.Threading.Dispatcher.Run();

}
  

И следующее взято из выходного класса

 public static void print(String str)
{
  Dispatcher uiDispatcher = OutputWindow.myOutputWindow.Dispatcher;
  uiDispatcher.BeginInvoke(new Action(delegate() { OutputWindow.myOutputWindow.textBoxOutput.AppendText(str   "n"); }));
  uiDispatcher.BeginInvoke(new Action(delegate() { OutputWindow.myOutputWindow.textBoxOutput.ScrollToLine(OutputWindow.myOutputWindow.textBoxOutput.LineCount - 1); }));
}

public static void printOnSameLine(String str)
{
  Dispatcher uiDispatcher = OutputWindow.myOutputWindow.Dispatcher;
  uiDispatcher.BeginInvoke(new Action(delegate() { OutputWindow.myOutputWindow.textBoxOutput.AppendText(str); }));
  uiDispatcher.BeginInvoke(new Action(delegate() { OutputWindow.myOutputWindow.textBoxOutput.ScrollToLine(OutputWindow.myOutputWindow.textBoxOutput.LineCount - 1); }));
}
  

«Begin» печатается в текстовом поле, а «FINAL» — нет, я хочу, чтобы метод start в тестовом классе обновлял текстовое поле в outputwindow на протяжении всей программы. Каков наилучший способ сделать это?

Заранее благодарю вас

Ответ №1:

Я не уверен, что вы пытаетесь сделать. Это нормально, что FINAL не печатается, потому что вы вызвали System.Windows.Многопоточность.Dispatcher.Run(). Этот метод поддерживает поток в рабочем состоянии и прослушивает события. Вы можете посмотреть на это так, как если бы у вас было while(true){} внутри метода Run. Метод будет продолжать выполняться до тех пор, пока диспетчер не завершит работу. Вы должны поддерживать фоновый поток активным и вызывать свои статические методы из другого потока, когда вам нужно установить сообщение. Вот пример:

         // reference to window in another thread
        Window outputWindow = null;

        Thread thread = new Thread(() =>
        {
            // another thread
            outputWindow = new Window();
            outputWindow.Show();
            // run event loop
            System.Windows.Threading.Dispatcher.Run();
        }) { ApartmentState = ApartmentState.STA, IsBackground = true };
        thread.Start();

        while (outputWindow == null)
        {
            // wait until the window in another thread has been created
            Thread.Sleep(100);
        }

        // simulate process
        for (int i = 0; i < 10; i  )
        {
            outputWindow.Dispatcher.BeginInvoke((Action)(() => { outputWindow.Title = i.ToString(); }), System.Windows.Threading.DispatcherPriority.Normal);
            Thread.Sleep(500); // simulate some hard work so we can see the change on another window's title
        }

        // close the window or shutdown dispatcher or abort the thread...
        thread.Abort();
  

Редактировать:

Это может быть быстрое и грязное универсальное решение. DoSomeHardWork создает другой поток GUI для окна ожидания, в котором отображается информация о ходе выполнения. В этом окне создается рабочий поток, который фактически выполняет работу. Работа реализована в методе Action. 1-й аргумент — окно ожидания, поэтому вы можете изменить его из рабочего потока. Конечно, в реальном мире вы должны перейти через интерфейс, а не напрямую к реализации window, но это всего лишь пример. 2-й аргумент — object, поэтому вы можете передать все, что вам нужно, в рабочий поток. Если вам нужны дополнительные аргументы, передайте object[] или измените подпись метода. В этом примере я имитирую тяжелую работу со счетчиком и режим ожидания. Вы можете выполнить этот код при нажатии кнопки несколько раз, и вы увидите, что все окна ожидания подсчитывают свой собственный счетчик без зависания. Вот код:

     public static void DoSomeHardWork(Action<Window, object> toDo, object actionParams)
    {
        Thread windowThread = new Thread(() =>
        {
            Window waitWindow = new Window();
            waitWindow.Loaded  = (s, e) =>
            {
                Thread workThread = new Thread(() =>
                {
                    // Run work method in work thread passing the
                    // wait window as parameter
                    toDo(waitWindow, actionParams);
                }) { IsBackground = true };
                // Start the work thread.
                workThread.Start();
            };
            waitWindow.Show();
            Dispatcher.Run();
        }) { ApartmentState = ApartmentState.STA, IsBackground = true };
        // Start the wait window thread.
        // When window loads, it will create work thread and start it.
        windowThread.Start();
    }

    private void MyWork(Window waitWindow, object parameters)
    {
        for (int i = 0; i < 10; i  )
        {
            // Report progress to user through wait window.
            waitWindow.Dispatcher.BeginInvoke((Action)(() => waitWindow.Title = string.Format("{0}: {1}", parameters, i)), DispatcherPriority.Normal);
            // Simulate long work.
            Thread.Sleep(500);
        }
        // The work is done. Shutdown the wait window dispather.
        // This will stop listening for events and will eventualy terminate
        // the wait window thread.
        waitWindow.Dispatcher.InvokeShutdown();
    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        DoSomeHardWork(MyWork, DateTime.Now);
    }
  

Комментарии:

1. Спасибо за ваш ответ и код. Это то, что я пытаюсь сделать: 1. При нажатии кнопки StartStop я хочу, чтобы метод в другом классе запускался и выполнялся в другом потоке. 2. метод «пуск» откроет новое окно, содержащее текстовое поле, и по мере работы обновит текстовое поле, чтобы предоставить пользователю обратную связь.

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

3. В этом случае я бы нормально открывал окно в текущем потоке GUI и выполнял всю остальную работу в фоновом потоке. Обновления в открытом окне могут передаваться непосредственно через GUI dispatcher. Не важно, как вы это организуете. Просто примите во внимание: 1. Если окно создается в фоновом потоке, вы должны установить поток как STA и вызвать Dispatcher.Run() после Show(). 2. Все вызовы к открытому окну должны проходить через его объект dispatcher. 3. Это окно «информация о ходе выполнения» не должно находиться в том же потоке, где вы выполняете свою обработку, иначе оно будет заблокировано. Если вам нужен какой-то код … прокомментируйте меня.

4. @user1018735 Спасибо, я был бы признателен за несколько примеров кода. Я пытался заставить это работать в течение некоторого времени. Я хотел бы создать несколько потоков Test.start (), а не только один, поэтому я хотел, чтобы новое окно открывалось из Test.start(). Я думал, что этим будет проще управлять. Пожалуйста, предложите лучший способ сделать это.

Ответ №2:

В идеале поток (UI thread), который создает элементы пользовательского интерфейса, также владеет элементами. С помощью диспетчера все, что вы делаете, это переводите обработку, не связанную с пользовательским интерфейсом, в фоновый поток. Как только фоновый процесс будет завершен, результат снова будет отправлен обратно в основной поток пользовательского интерфейса. Для примера ознакомьтесь с:http://weblogs.asp.net/pawanmishra/archive/2010/06/06/understanding-dispatcher-in-wpf.aspx