在我的主窗口(线程A )中,我启动了一个新线程(线程B ),它在用户等待时完成了一些工作。
如果出现错误或用户需要额外信息,线程B将触发事件,线程A将侦听这些事件。
在线程A的事件监听器中,我需要向用户显示对话框消息,我有一个自定义对话框窗口并使用dialogWindow.showDialog()
显示它。这工作正常,但在我尝试设置对话框的所有者时会导致错误,我执行此操作dialogWindow.Owner = Window.GetWindow(this)
。
我得到的错误是:调用线程无法访问此对象,因为另一个线程拥有它。
侦听从其他线程触发的事件的正确方法是什么?
答案 0 :(得分:7)
事件侦听器代码将在触发事件的线程中隐式运行,因此事件侦听器不是线程绑定的。
如果您希望在事件处理过程中在UI中显示某些内容,则应自行进行编组。这样的事情:
void OnEvent(object sender, EventArgs args)
{
// runs in the event sender's thread
string x = ComputeChanges(args);
Dispatcher.BeginInvoke((Action)(
() => UpdateUI(x)
));
}
void UpdateUI(string x)
{
// runs in the UI thread
control.Content = x;
// - or -
new DialogWindow() { Content = x, Owner = this }.ShowDialog();
// - or whatever
}
所以:你在后台线程中优先执行计算(如果有的话),而不触及UI;之后,当您知道UI中需要进行哪些更改时,您可以在UI线程中执行UI更新代码。
Dispatcher
是控件的属性,因此如果您的代码是UI的一部分,您将免费获得Dispatcher。否则,您可以从任何控件(例如control.Dispatcher
)中获取调度程序。
答案 1 :(得分:3)
当然 - >我们所做的是使用SynchronizationContext。 因此,当您启动一个新线程时,您捕获(在UI线程上)当前上下文并将其作为参数传递给第二个线程。
然后在第二个帖子上,当你想举起一个事件时,你就这样做了:
if (_uiThreadId != Thread.CurrentThread.ManagedThreadId)
{
_uiContext.Post(
new SendOrPostCallback(delegate(object o) { OnYourEvent((EventArgs)o); }),
e);
}
else
OnYourEvent(e);
答案 2 :(得分:3)
从后台线程向UI线程引发事件的正确方法是,应该在该Dispatcher上引发事件, 这里的关键是获得UIthread的调度员。
UIDisaptcher.BeginInvoke((ThreadStart)(()=> RaiseEventToUIThread()));
当UI线程侦听raise事件时,它可以设置Owner属性(因为窗口是由UI线程创建的)。