【发布时间】:2012-08-09 01:35:19
【问题描述】:
在 WPF 中使用多个线程时可能遇到的一个常见异常是:
调用线程无法访问此对象,因为不同的线程拥有它
有哪些方法可以正确处理这个问题?
【问题讨论】:
标签: wpf multithreading
在 WPF 中使用多个线程时可能遇到的一个常见异常是:
调用线程无法访问此对象,因为不同的线程拥有它
有哪些方法可以正确处理这个问题?
【问题讨论】:
标签: wpf multithreading
根据情况有多种选择:
从另一个线程访问控件
例如使用进度信息更新 TextBlock。
在这种情况下,您可以做的最简单的事情是避免与控件直接交互。您可以将要访问或修改的属性bind 设置为类implements INotifyPropertyChanged 的对象,然后在该对象上设置属性。该框架将为您处理其余部分。 (一般来说,您很少需要直接与 UI 元素交互,您几乎总是可以绑定相应的属性并使用绑定源来代替;可能需要直接控制访问的一种情况是控件创作。)
在某些情况下,仅数据绑定是不够的,例如当尝试修改绑定 ObservableCollection<T> 时,为此您需要...
您可以将访问代码分派给拥有该对象的线程,这可以通过在拥有被访问对象的Dispatcher 上调用Invoke 或BeginInvoke 来完成(在另一个线程上可以得到这个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);
}
如果一个对象不是DispatcherObject,并且您仍然需要关联的Dispatcher,您可以在创建对象的线程中使用Dispatcher.CurrentDispatcher(在正在执行的方法中执行此操作线程不会对你有任何好处)。为方便起见,您通常在应用程序的主 UI 线程上创建对象;您可以使用Application.Current.Dispatcher 从任何地方获取该线程的Dispatcher。
特殊情况:
将任何控制访问移至ProgressChanged,因为它发生在创建实例的线程(当然应该是 UI 线程)上
计时器
在 WPF 中,为方便起见,您可以使用 DispatcherTimer,它会为您执行调度,因此在关联的调度程序上调用 Tick 中的任何代码。如果您可以将调度委托给数据绑定系统,那么您当然也可以使用普通计时器。
您可以阅读更多关于Dispatcher 队列的工作原理和一般的 WPF 线程on MSDN。
访问在另一个线程上创建的对象
例如在后台加载图像。
如果有问题的对象不是Freezable,您通常应该避免在另一个线程上创建它或限制对创建线程的访问。如果是Freezable,则只需调用Freeze 即可使其可供其他线程访问。
从另一个线程访问数据对象
也就是说,正在更新其实例的类型是用户代码。如果抛出异常,这种情况可能是有人使用DependencyObject 作为数据类的基本类型造成的。
这种情况与访问控件相同,可以应用相同的方法,但通常应首先避免。当然,这允许通过dependency properties 进行简单的属性更改通知,并且这些属性也可以绑定,但通常这不值得放弃线程独立性。您可以从INotifyPropertyChanged 获得更改通知,并且 WPF 中的绑定系统本质上是不对称的,总是有一个被绑定的属性(目标)和作为此绑定源的东西。通常 UI 是目标,数据是源,这意味着只有 UI 组件才需要依赖属性。
【讨论】:
这将是几百行代码,我“想通了”。
但总结是:
App_OnStartup 生成后台线程
在回调中,
打电话
Application.Current.MainWindow.Dispatcher.CheckAccess() - 获取异常 Application.Current.Dispatcher.CheckAccess() 没有
【讨论】:
Current.MainWindow,而不是MainWindow.Dispatcher,所以是的,这不是我的建议......
我有一个 udp 侦听器对象,它通过事件进行通信,其中方法/回调在我的 mainWindow wpf .cs 文件中被 +='ed。
使用参数调用事件处理函数,其中一个是我希望在 mainWindow.cs 的列表框中显示的消息
使用 H.B. 在此线程中的信息。更多; 我使用以下代码在我的事件处理程序回调中添加、测试和处理了 wpf 中的交叉线程,但我使用的是真实消息而不是硬编码消息:
listBox1.Dispatcher.Invoke(new Action(() => listBox1.Items.Add("MessageHere")));
更新:
这样更好,因为您可以在匿名函数中放入更多内容。
listBox1.Dispatcher.Invoke((Action)delegate
{
listBox1.Items.Add(e.ReaderMessage);
});
【讨论】: