wpf 如何处理跨线程访问异常?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11923865/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to deal with cross-thread access exceptions?
提问by H.B.
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?
有什么选择可以正确处理这个问题?
回答by H.B.
Depending on the situation there are various options:
根据情况有多种选择:
Accessing a control from another thread
从另一个线程访问控件
e.g. updating a TextBlock with progress information.
例如,使用进度信息更新 TextBlock。
In this case the easiest thing you can do is avoiding the direct interaction with the control. You can just bindthe property you want to access or modify to an object whose class implements
INotifyPropertyChangedand 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
InvokeorBeginInvokeon theDispatcherowning the object being accessed (getting thisDispatcheris 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.CheckAccessto 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
DispatcherObjectand you still need the associatedDispatcheryou can useDispatcher.CurrentDispatcherin 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'sDispatcherfrom anywhere usingApplication.Current.Dispatcher.
数据绑定:
在这种情况下,您可以做的最简单的事情是避免与控件的直接交互。您可以将要访问或修改的属性绑定到其类实现的对象,
INotifyPropertyChanged然后在该对象上设置该属性。该框架将为您处理其余的工作。(一般来说,您很少需要直接与 UI 元素交互,您几乎总是可以绑定相应的属性并使用绑定源来代替;一种可能需要直接控制访问的情况是控件创作。)在某些情况下,仅数据绑定是不够的,例如在尝试修改 bound 时
ObservableCollection<T>,为此您需要...调度:
您可以发送您的访问代码的线程拥有的对象,这可以通过调用来完成
Invoke或BeginInvoke在Dispatcher拥有该对象被访问(这越来越Dispatcher有可能在另一个线程)。例如
new Thread(ThisThreadStart).Start();void ThisThreadStart() { textBlock.Dispatcher.Invoke(new Action(() => textBlock.Text = "Test")); }如果不清楚在哪个线程上执行方法,您可以使用
Dispatcher.CheckAccess直接分派或执行操作。例如
void Update() { Action action = () => myTextBlock.Text = "Test"; var dispatcher = myTextBlock.Dispatcher; if (dispatcher.CheckAccess()) action(); else dispatcher.Invoke(action); }如果对象不是 a
DispatcherObject并且您仍然需要关联的,Dispatcher您可以在创建对象的线程中使用(因此在线程执行的方法中执行此操作对您没有任何好处)。为方便起见,您通常在应用程序的主 UI 线程上创建对象;你可以从任何地方使用.Dispatcher.CurrentDispatcherDispatcherApplication.Current.Dispatcher
Special cases:
特别案例:
Move any control access to
ProgressChangedas it occurs on the thread that created the instance (which should of course be the UI-thread)Timers
In WPF you can use the
DispatcherTimerfor convenience, it does the dispatching for you so any code inTickis 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.
移动任何控制访问,
ProgressChanged因为它发生在创建实例的线程上(当然应该是 UI 线程)计时器
在 WPF 中,
DispatcherTimer为了方便起见,您可以使用,它为您执行调度,因此Tick在关联的调度程序上调用任何代码。如果您可以将调度委托给数据绑定系统,您当然也可以使用普通计时器。
You can read more about how the Dispatcherqueue 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 Freezableyou should in general simply avoid creating it on another thread or restricting access to the creating thread. If it is Freezableyou just need to call Freezeto make it accessible to other threads.
如果有问题的对象不是Freezable您通常应该避免在另一个线程上创建它或限制对创建线程的访问。如果是,Freezable您只需要调用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 DependencyObjectas base type for a data class.
也就是说,其实例正在更新的类型是用户代码。如果抛出异常,这种情况可能是由使用DependencyObject数据类的基类型的人造成的。
This situation is the same as accessing a controland 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 propertiesand those properties can also be bound but often enough this is just not worth giving up thread-independency. You can get change notifications from INotifyPropertyChangedand 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 中的绑定系统本质上是不对称的,总是有一个被绑定的属性(目标)和某些东西是此绑定的源。通常 UI 是目标,数据是源,这意味着只有 UI 组件需要依赖属性。
回答by Roger Willcocks
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() - 获取异常 Application.Current.Dispatcher.CheckAccess() 没有
回答by David Carlson
I have a udp listener object that communicates through events where the method/callbacks are +='ed in my mainWindow wpf .cs file.
我有一个 udp 侦听器对象,它通过事件进行通信,其中方法/回调在我的 mainWindow 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:
上面 HB 使用此线程中的信息;我使用以下代码在我的 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);
});

