必须在与 DependencyObject 相同的线程上创建 DependencySource
Posted
技术标签:
【中文标题】必须在与 DependencyObject 相同的线程上创建 DependencySource【英文标题】:Must create DependencySource on same Thread as the DependencyObject 【发布时间】:2011-06-09 02:43:11 【问题描述】:我将可观察字典从视图模型绑定到视图。我使用 Caliburn 微框架。
查看:
<ListBox Name="Friends"
SelectedIndex="Binding Path=SelectedFriendsIndex,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged"
SelectedItem="Binding Path=SelectedFriend, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged"
Style="DynamicResource friendsListStyle"
IsTextSearchEnabled="True" TextSearch.TextPath="Value.Nick"
Grid.Row="2"
Margin="4,4,4,4"
PreviewMouseRightButtonUp="ListBox_PreviewMouseRightButtonUp"
PreviewMouseRightButtonDown="ListBox_PreviewMouseRightButtonDown"
MouseRightButtonDown="ListBox_MouseRightButtonDown"
Micro:Message.Attach="[MouseDoubleClick]=[Action OpenChatScreen()]" >
来自视图模型类的代码。
属性如下所示:
public MyObservableDictionary<string, UserInfo> Friends
get return _friends;
set
_friends = value;
NotifyOfPropertyChange(() => Friends);
在调度程序计时器中,我每 3 秒在单独的线程新服务方法中调用一次。
所以我的视图模型构造函数我有这个:
_dispatcherTimer = new DispatcherTimer();
_dispatcherTimer.Tick += DispatcherTimer_Tick;
_dispatcherTimer.Interval = TimeSpan.FromSeconds(3);
_dispatcherTimer.Start();
_threadDispatcher = Dispatcher.CurrentDispatcher;
而Timer的tick方法在这里:
private void DispatcherTimer_Tick(object sender, EventArgs eventArgs)
new System.Threading.Tasks.Task(() =>
//get new data from server
MyObservableDictionary<string, UserInfo> freshFriends = _service.GetFriends(Account);
_threadDispatcher.BeginInvoke((System.Action)(() =>
//clear data, Friend is property which is binded on listobox control
Friends.Clear();
//here is problem - > refresh data
foreach (var freshFriend in freshFriends)
Friends.Add(freshFriend);
));
).Start();
当我运行应用程序时出现此错误:
Must create DependencySource on same Thread as the DependencyObject.
at System.Windows.Markup.XamlReader.RewrapException(Exception e, Uri baseUri)
at System.Windows.FrameworkTemplate.LoadTemplateXaml(XamlReader templateReader, XamlObjectWriter currentWriter)
at System.Windows.FrameworkTemplate.LoadTemplateXaml(XamlObjectWriter objectWriter)
at System.Windows.FrameworkTemplate.LoadOptimizedTemplateContent(DependencyObject container, IComponentConnector componentConnector, IStyleConnector styleConnector, List`1 affectedChildren, UncommonField`1 templatedNonFeChildrenField)
at System.Windows.FrameworkTemplate.LoadContent(DependencyObject container, List`1 affectedChildren)
at System.Windows.StyleHelper.ApplyTemplateContent(UncommonField`1 dataField, DependencyObject container, FrameworkElementFactory templateRoot, Int32 lastChildIndex, HybridDictionary childIndexFromChildID, FrameworkTemplate frameworkTemplate)
at System.Windows.FrameworkTemplate.ApplyTemplateContent(UncommonField`1 templateDataField, FrameworkElement container)
at System.Windows.FrameworkElement.ApplyTemplate()
at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
at System.Windows.UIElement.Measure(Size availableSize)
at System.Windows.Controls.Border.MeasureOverride(Size constraint)
我尝试更换调度员:
这个_threadDispatcher = Dispatcher.CurrentDispatcher;
用这个:_threadDispatcher = Application.Current.Dispatcher;
但这无济于事。感谢您的建议。
MyObservableDicionary 不是依赖对象或具有依赖属性:
public class MyObservableDictionary<TKey, TValue> :
IDictionary<TKey, TValue>,
INotifyCollectionChanged,
INotifyPropertyChanged
..
【问题讨论】:
【参考方案1】:您的数据源是 DependencyObject 吗?如果是这样,它也需要在 UI 线程上创建。 通常,您不需要从 DependencyObject 继承数据源。
【讨论】:
数据源是什么意思?物业朋友? 您的视图模型,或任何绑定到 UI 的东西。 不,VM 是用 Caliburn Micro 创建的,在这个类中我没有任何依赖属性和对象。 也许是你的 ObservableDictionary?尝试在 UI 线程上创建它。 我在VM的构造函数中尝试初始化变量freshFriends超出范围dispatcherTimer_tick事件。但问题是一样的。 MyObservable dic 在我看来是可以的,在这个类中没有依赖属性或对象。【参考方案2】:只是猜测,但默认情况下,任务是在后台线程上创建的。尝试使用带有 SynchronizationContext 的 Task.Factory 重载来创建您的任务。我不确定在 Task 中使用 Dispatcher 是否按预期方式工作。
var uiContext = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() => , CancellationToken.None, TaskCreationOptions.None, uiContext);
一旦你这样做了,你应该能够在不使用调度程序的情况下修改你的支持属性。
【讨论】:
很好的答案 - 是的,这种方法确实有效,解决了我长期以来一直在努力解决的问题。 但这不再在后台执行任务了,对吧?它变成了一个用户界面操作。【参考方案3】:为了完整起见,我会提到,如果您有一些不继承 Freezable 类的对象,则批准的答案不适合。标准的 ObservableCollection 只允许从调度程序线程进行更新,因此您需要一个线程安全的类比。 WPF大师Dean Chalk有两种解决方案解决的问题:
-
创建一个线程安全的可观察集合。这是一个老派的解决方案,只是有效。要获取源代码,请查看short article in his blog。
使用Reactive Extensions 库。有关示例,请参阅this article。它对于一项任务来说有点笨重,但同时它带来了一堆派上用场的现代工具。
更新(2015 年 7 月 31 日):
Dean Chalk 博客的链接已失效,所以这里有替代方案:
线程安全的可观察集合:article、source code。 多线程、ObservableCollection、反应式扩展:article。【讨论】:
您的链接已失效;(也许您可以将信息复制到此处而不是链接到其他地方? @JumpingJezza,我添加了替代链接。这两种解决方案对于帖子来说都太庞大了。 不用担心。我有一个 ImageSource 类型的 Observable 集合,只需冻结 ImageSource 即可。【参考方案4】:我遇到了类似的情况。 我将名为 Person 的类的 ObservableCollection 绑定到数据网格,并且 Person.SkinColor 是 SolidColorBrush。 我所做的如下:
foreach (Person person in personData)
PersonModel person= new Person( );
......
personModel.SkinColor = new SolidColorBrush(person.FavoriteColor);
personModel.SkinColor.Freeze();
.....
【讨论】:
我在 ImageSource 上遇到了同样的问题。在我的情况下,调用 Freeze() 也有效以上是关于必须在与 DependencyObject 相同的线程上创建 DependencySource的主要内容,如果未能解决你的问题,请参考以下文章
自定义 DependencyProperty 与 RoutedEvent
ViewModel应该继承WPF中的DependencyObject吗?
只能在 DependencyObject 的 DependencyProperty 上设置“绑定”