#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