在使用WPF的多线程时,常见的异常之一是:
调用线程无法访问此对象,因为不同的线程拥有它
如何正确处理这个问题?
根据情况,有多种选择:
从另一个线程访问控件
例如更新TextBlock的进度信息。
In this case the easiest thing you can do is avoiding the direct interaction with the control. You can just bind the property you want to access or modify to an object whose class implements INotifyPropertyChanged
and then set the property on that object instead. The framework will handle the rest for you. (In general you rarely should need to interact with UI-elements directly, you can almost always bind the respective properties and work with the binding source instead; one case where direct control access may be necessary is control authoring.)
There are some cases where data binding alone is not enough, for example when trying to modify a bound ObservableCollection<T>
, for this you need...
You can dispatch your accessing code to the thread owning the object, this can be done by calling Invoke
or BeginInvoke
on the Dispatcher
owning the object being accessed (getting this Dispatcher
is possible on another thread).
e.g.
new Thread(ThisThreadStart).Start();
void ThisThreadStart()
{
textBlock.Dispatcher.Invoke(new Action(() => textBlock.Text = "Test"));
}
If it is not clear on which thread a method is executed you can use Dispatcher.CheckAccess
to either dispatch or execute an action directly.
e.g.
void Update()
{
Action action = () => myTextBlock.Text = "Test";
var dispatcher = myTextBlock.Dispatcher;
if (dispatcher.CheckAccess())
action();
else
dispatcher.Invoke(action);
}
If an object is not a DispatcherObject
and you still need the associated Dispatcher
you can use Dispatcher.CurrentDispatcher
in the thread creating the object (so doing this in the method being executed by a thread will not do you any good). For convenience as you usually create objects on the application's main UI thread; you can get that thread's Dispatcher
from anywhere using Application.Current.Dispatcher
.
特殊情况:
将任何控件访问移动到发生在创建实例的线程上(当然应该是UI线程)的ProgressChanged
中。
计时器
在WPF中,您可以使用DispatcherTimer
以方便地进行分派,它会为您进行分派,因此Tick
中的任何代码都会在关联的调度程序上调用。如果您可以将分派委托给数据绑定系统,则当然也可以使用普通计时器。
您可以在MSDN上了解有关 Dispatcher
队列如何工作以及WPF线程的更多信息。
访问在另一个线程创建的对象
例如,在后台加载图像。
如果所讨论的对象不是Freezable
,通常应该避免在另一个线程上创建它或限制对创建线程的访问。如果它是Freezable
,只需要调用Freeze
使其可被其他线程访问。
从另一个线程访问数据对象
也就是说,正在更新实例的类型是用户代码。如果抛出异常,则可能是由于某个人将DependencyObject
用作数据类的基类型。
INotifyPropertyChanged
和WPF中的绑定系统获取更改通知,它本质上是不对称的,总是有一个绑定的属性(目标)和作为此绑定源的东西。通常UI是目标,数据是源,这意味着只有UI组件需要依赖属性。这将是几百行代码,为了我“弄清楚”的东西。
但总结如下:
App_OnStartup 生成一个后台线程
在回调函数中,
调用
Application.Current.MainWindow.Dispatcher.CheckAccess() - 抛出异常 Application.Current.Dispatcher.CheckAccess() 不会抛出异常
Current.MainWindow
这一步,而不是MainWindow.Dispatcher
,所以是的,我不建议这样做... - H.B.我有一个UDP监听器对象,通过事件进行通信,在我的mainWindow wpf .cs文件中添加方法/回调。
事件处理程序函数被调用时带有参数,其中一个是我想在mainWindow.cs中的listbox中显示的消息。
使用H.B.在此线程中提供的信息; 我已经添加、测试并处理了WPF中的crossthread,在我的事件处理程序回调中使用以下代码,但我使用的是真实的消息而不是硬编码的消息:
listBox1.Dispatcher.Invoke(new Action(() => listBox1.Items.Add("MessageHere")));
更新:
这样做更好,因为您可以在匿名函数中放入更多内容。
listBox1.Dispatcher.Invoke((Action)delegate
{
listBox1.Items.Add(e.ReaderMessage);
});