A common exception one can get when working with multiple threads in WPF is:
在WPF中使用多个线程时,一个常见的异常是:
The calling thread cannot access this object because a different thread owns it
调用线程不能访问这个对象,因为不同的线程拥有它。
What are the options to deal with this properly?
如何妥善处理这一问题?
3 个解决方案
#1
34
Depending on the situation there are various options:
视情况而定,有各种选择:
Accessing a control from another thread
从另一个线程访问控件
e.g. updating a TextBlock with progress information.
例如,用进度信息更新文本块。
-
数据绑定:
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.)在这种情况下,最简单的方法就是避免与控件的直接交互。您可以将要访问或修改的属性绑定到其实现INotifyPropertyChanged的对象上,然后在该对象上设置属性。框架将为您处理其余的。(一般来说,您很少需要直接与ui元素交互,您几乎总是可以绑定各自的属性并使用绑定源;一种可能需要直接控制访问的情况是控制创作。
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...有些情况下,单凭数据绑定是不够的,例如,当试图修改绑定的ObservableCollection
时,您需要…… -
调度:
You can dispatch your accessing code to the thread owning the object, this can be done by calling
Invoke
orBeginInvoke
on theDispatcher
owning the object being accessed (getting thisDispatcher
is possible on another thread).您可以将访问代码分派给拥有对象的线程,这可以通过调用拥有被访问对象的Dispatcher上的Invoke或BeginInvoke来实现(在另一个线程中可以获得此Dispatcher)。
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.如果不清楚执行方法的线程,可以使用Dispatcher。对调度或直接执行操作的检查访问。
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 associatedDispatcher
you can useDispatcher.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'sDispatcher
from anywhere usingApplication.Current.Dispatcher
.如果对象不是DispatcherObject,并且仍然需要关联的Dispatcher,那么可以使用Dispatcher。在创建对象的线程中创建CurrentDispatcher(因此在线程执行的方法中这样做对您没有任何好处)。为方便起见,通常在应用程序的主UI线程上创建对象;您可以使用Application.Current.Dispatcher从任何地方获得该线程的分派器。
Special cases:
特殊情况:
-
BackgroundWorker
Move any control access to
ProgressChanged
as it occurs on the thread that created the instance (which should of course be the UI-thread)在创建实例的线程(当然应该是UI-thread)上更改任何对progress的控制访问
-
Timers
计时器
In WPF you can use the
DispatcherTimer
for convenience, it does the dispatching for you so any code inTick
is invoked on the associated dispatcher. If you can delegate the dispatching to the data binding system you of course can use a normal timer as well.在WPF中,为了方便,您可以使用DispatcherTimer,它为您执行分派,因此在关联的dispatcher上调用任何带Tick的代码。如果可以将分派委托给数据绑定系统,当然也可以使用正常的计时器。
You can read more about how the Dispatcher
queue works and WPF threading in general on MSDN.
您可以在MSDN上阅读更多关于Dispatcher队列如何工作和WPF线程的信息。
Accessing an object created on another thread
访问在另一个线程上创建的对象
e.g. loading an image in the background.
在背景中载入图像。
If the object in question is not Freezable
you should in general simply avoid creating it on another thread or restricting access to the creating thread. If it is Freezable
you just need to call Freeze
to make it accessible to other threads.
如果所讨论的对象不是可冻结的,您通常应该避免在另一个线程上创建它或限制对创建线程的访问。如果它是可冻结的,你只需要调用Freeze来使其他线程可以访问它。
Accessing a data object from another thread
从另一个线程访问数据对象。
That is, the type whose instance is being updated is user-code. If an exception is thrown this situation probably came about by someone using DependencyObject
as base type for a data class.
也就是说,正在更新实例的类型是用户代码。如果抛出异常,这种情况可能是由使用DependencyObject作为数据类基类型的人造成的。
This situation is the same as accessing a control and the same approaches can be applied but usually it should be avoided in the first place. Granted, this allows for simple property change notifications via dependency properties and those properties can also be bound but often enough this is just not worth giving up thread-independency. You can get change notifications from INotifyPropertyChanged
and the binding system in WPF is inherently asymmetrical, there always is a property that is bound (target) and something that is the source for this binding. Usually the UI is the target and the data is the source, meaning that only UI components should need dependency properties.
这种情况与访问控件相同,可以使用相同的方法,但通常应该首先避免这种情况。当然,这允许通过依赖属性进行简单的属性更改通知,这些属性也可以被绑定,但通常这并不值得放弃线程独立性。您可以从INotifyPropertyChanged中获得更改通知,WPF中的绑定系统本质上是不对称的,总是有一个绑定的属性(target)和这个绑定的源。通常UI是目标,数据是源,这意味着只有UI组件需要依赖属性。
#2
0
That would be several hundred lines of code, for something I "figured out".
那将是几百行代码,对于我“发现”的东西。
But the summary is:
但总结是:
App_OnStartup generate a background thread
App_OnStartup生成一个后台线程
in the callback,
在回调,
Call
调用
Application.Current.MainWindow.Dispatcher.CheckAccess() - gets the exception Application.Current.Dispatcher.CheckAccess() does not
Application.Current.MainWindow.Dispatcher.CheckAccess() -获取异常应用程序。current. dispatcher.checkaccess()没有。
#3
0
I have a udp listener object that communicates through events where the method/callbacks are +='ed in my mainWindow wpf .cs file.
我有一个udp监听器对象,它通过在我的主窗口wpf .cs文件中方法/回调为+='ed的事件进行通信。
The event handler functions are called with parameters, one being the message I want displayed in a listbox in the mainWindow.cs
事件处理函数使用参数调用,其中一个是我想要显示在mainWindow.cs中的列表框中的消息。
Using the information in this thread by H.B. above; I have added, tested and handled the crossthread in wpf in my eventhandler callback using the following code, but I use a real message not a hard coded one:
使用以上H.B.的资料;我已经使用以下代码在我的eventhandler回调中添加、测试和处理wpf中的跨线程,但是我使用了一个真正的消息,而不是硬编码的消息:
listBox1.Dispatcher.Invoke(new Action(() => listBox1.Items.Add("MessageHere")));
UPDATE:
更新:
This is better because you can put more things in the anonymous function.
这更好,因为您可以在匿名函数中添加更多的东西。
listBox1.Dispatcher.Invoke((Action)delegate
{
listBox1.Items.Add(e.ReaderMessage);
});
#1
34
Depending on the situation there are various options:
视情况而定,有各种选择:
Accessing a control from another thread
从另一个线程访问控件
e.g. updating a TextBlock with progress information.
例如,用进度信息更新文本块。
-
数据绑定:
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.)在这种情况下,最简单的方法就是避免与控件的直接交互。您可以将要访问或修改的属性绑定到其实现INotifyPropertyChanged的对象上,然后在该对象上设置属性。框架将为您处理其余的。(一般来说,您很少需要直接与ui元素交互,您几乎总是可以绑定各自的属性并使用绑定源;一种可能需要直接控制访问的情况是控制创作。
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...有些情况下,单凭数据绑定是不够的,例如,当试图修改绑定的ObservableCollection
时,您需要…… -
调度:
You can dispatch your accessing code to the thread owning the object, this can be done by calling
Invoke
orBeginInvoke
on theDispatcher
owning the object being accessed (getting thisDispatcher
is possible on another thread).您可以将访问代码分派给拥有对象的线程,这可以通过调用拥有被访问对象的Dispatcher上的Invoke或BeginInvoke来实现(在另一个线程中可以获得此Dispatcher)。
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.如果不清楚执行方法的线程,可以使用Dispatcher。对调度或直接执行操作的检查访问。
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 associatedDispatcher
you can useDispatcher.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'sDispatcher
from anywhere usingApplication.Current.Dispatcher
.如果对象不是DispatcherObject,并且仍然需要关联的Dispatcher,那么可以使用Dispatcher。在创建对象的线程中创建CurrentDispatcher(因此在线程执行的方法中这样做对您没有任何好处)。为方便起见,通常在应用程序的主UI线程上创建对象;您可以使用Application.Current.Dispatcher从任何地方获得该线程的分派器。
Special cases:
特殊情况:
-
BackgroundWorker
Move any control access to
ProgressChanged
as it occurs on the thread that created the instance (which should of course be the UI-thread)在创建实例的线程(当然应该是UI-thread)上更改任何对progress的控制访问
-
Timers
计时器
In WPF you can use the
DispatcherTimer
for convenience, it does the dispatching for you so any code inTick
is invoked on the associated dispatcher. If you can delegate the dispatching to the data binding system you of course can use a normal timer as well.在WPF中,为了方便,您可以使用DispatcherTimer,它为您执行分派,因此在关联的dispatcher上调用任何带Tick的代码。如果可以将分派委托给数据绑定系统,当然也可以使用正常的计时器。
You can read more about how the Dispatcher
queue works and WPF threading in general on MSDN.
您可以在MSDN上阅读更多关于Dispatcher队列如何工作和WPF线程的信息。
Accessing an object created on another thread
访问在另一个线程上创建的对象
e.g. loading an image in the background.
在背景中载入图像。
If the object in question is not Freezable
you should in general simply avoid creating it on another thread or restricting access to the creating thread. If it is Freezable
you just need to call Freeze
to make it accessible to other threads.
如果所讨论的对象不是可冻结的,您通常应该避免在另一个线程上创建它或限制对创建线程的访问。如果它是可冻结的,你只需要调用Freeze来使其他线程可以访问它。
Accessing a data object from another thread
从另一个线程访问数据对象。
That is, the type whose instance is being updated is user-code. If an exception is thrown this situation probably came about by someone using DependencyObject
as base type for a data class.
也就是说,正在更新实例的类型是用户代码。如果抛出异常,这种情况可能是由使用DependencyObject作为数据类基类型的人造成的。
This situation is the same as accessing a control and the same approaches can be applied but usually it should be avoided in the first place. Granted, this allows for simple property change notifications via dependency properties and those properties can also be bound but often enough this is just not worth giving up thread-independency. You can get change notifications from INotifyPropertyChanged
and the binding system in WPF is inherently asymmetrical, there always is a property that is bound (target) and something that is the source for this binding. Usually the UI is the target and the data is the source, meaning that only UI components should need dependency properties.
这种情况与访问控件相同,可以使用相同的方法,但通常应该首先避免这种情况。当然,这允许通过依赖属性进行简单的属性更改通知,这些属性也可以被绑定,但通常这并不值得放弃线程独立性。您可以从INotifyPropertyChanged中获得更改通知,WPF中的绑定系统本质上是不对称的,总是有一个绑定的属性(target)和这个绑定的源。通常UI是目标,数据是源,这意味着只有UI组件需要依赖属性。
#2
0
That would be several hundred lines of code, for something I "figured out".
那将是几百行代码,对于我“发现”的东西。
But the summary is:
但总结是:
App_OnStartup generate a background thread
App_OnStartup生成一个后台线程
in the callback,
在回调,
Call
调用
Application.Current.MainWindow.Dispatcher.CheckAccess() - gets the exception Application.Current.Dispatcher.CheckAccess() does not
Application.Current.MainWindow.Dispatcher.CheckAccess() -获取异常应用程序。current. dispatcher.checkaccess()没有。
#3
0
I have a udp listener object that communicates through events where the method/callbacks are +='ed in my mainWindow wpf .cs file.
我有一个udp监听器对象,它通过在我的主窗口wpf .cs文件中方法/回调为+='ed的事件进行通信。
The event handler functions are called with parameters, one being the message I want displayed in a listbox in the mainWindow.cs
事件处理函数使用参数调用,其中一个是我想要显示在mainWindow.cs中的列表框中的消息。
Using the information in this thread by H.B. above; I have added, tested and handled the crossthread in wpf in my eventhandler callback using the following code, but I use a real message not a hard coded one:
使用以上H.B.的资料;我已经使用以下代码在我的eventhandler回调中添加、测试和处理wpf中的跨线程,但是我使用了一个真正的消息,而不是硬编码的消息:
listBox1.Dispatcher.Invoke(new Action(() => listBox1.Items.Add("MessageHere")));
UPDATE:
更新:
This is better because you can put more things in the anonymous function.
这更好,因为您可以在匿名函数中添加更多的东西。
listBox1.Dispatcher.Invoke((Action)delegate
{
listBox1.Items.Add(e.ReaderMessage);
});