我有一个小的 WPF / MVVM 示例项目,包含两个可视元素(一个ComboBox
和一个简单的TextBlock
)。这两个元素都绑定到我的ViewModel的属性:
public const string WelcomeTitlePropertyName = "WelcomeTitle";
private string _welcomeTitle = string.Empty;
public string WelcomeTitle
{
get{ return _welcomeTitle;}
set
{
_welcomeTitle = value;
RaisePropertyChanged(WelcomeTitlePropertyName);
}
}
public const string PositionsPropertyName = "Positions";
private ObservableCollection<int> _positions = new ObservableCollection<int>();
public ObservableCollection<int> Positions
{
get{ return _positions; }
set
{
_positions = value;
RaisePropertyChanged(PositionsPropertyName);
}
}
<StackPanel>
<TextBlock Text="{Binding WelcomeTitle}"/>
<ComboBox ItemsSource="{Binding Positions}" />
</StackPanel>
现在我从非UI线程更改这两个属性(据我所知,这是不允许的):
System.Threading.ThreadPool.QueueUserWorkItem(delegate
{
int i = 0;
while(true)
{
Positions.Add(i); // Solution 1: this throws NotSupportedException
WelcomeTitle = i.ToString(); // Solution 2: this works
i++;
}
}, null);
问题:
为什么解决方案1抛出NotSupportedExpection
(不允许从非调度程序线程更改集合),而解决方案2按预期工作?
答案 0 :(得分:2)
现在我从非UI线程中更改这两个属性(就是这样) 据我所知,不允许)
一般情况下,无论您使用什么线程,更改属性值都非常精细。当更改属性具有“有趣”的副作用时,问题和限制可能会出现。
在这种情况下,两个被更改的属性都会产生有趣的副作用,并且观察到的行为的差异是由于这些副作用(来自框架代码,您无法直接看到)以不同的方式处理。 / p>
为什么解决方案1抛出NotSupportedExpection(不允许) 解决方案2工作时,从非调度程序线程更改集合 根据需要?
当更改绑定源的属性时,WPF绑定系统通过对UI进行相应的更新来响应;但是,当从后台线程进行更改时,绑定系统的事件处理程序也将在后台线程中运行,并且无法直接更新UI。对于这里涉及的两种情况都是如此。
不同之处在于,对于“简单”属性值更改,绑定系统会自动检测到它没有响应UI线程上的更改,并使用Dispatcher.Invoke
将UI更改分派给正确的线程,但是当可观察时集合被修改此调度不会自动发生。结果是更新UI的代码在后台线程上运行并抛出异常。
解决方案
有两件事可以解决这个问题:
直接在UI线程中更改属性
如果在UI线程上进行了更改,那么任何PropertyChanged
处理程序也将在UI线程上运行,因此他们可以自由地进行他们想要的任何UI更改。此解决方案可以在您自己的代码中强制执行,并且永远不会导致问题,但如果不需要更改UI,则无需任何好处即可完成调度到UI线程的额外工作。
确保PropertyChanged
处理程序调度与UI线程相关的任何UI相关更改
此解决方案的好处是它只按需调度工作,但也有必须明确编程事件处理程序(可能不是您自己的代码)以进行调度的缺点。 .NET已经为普通属性执行此操作,但不对ObservableCollection
执行此操作。有关详细信息,请参阅How do I update an ObservableCollection via a worker thread?。
答案 1 :(得分:1)
简单的Property绑定由WPF自动分派到GUI线程,并且可以从非UI线程更改。但是,对于集合更改(ObservableCollection<> BindingList<>
),情况并非如此。这些更改必须发生在创建控件的UI线程上。
如果我没记错的话,在WPF和.NET的早期阶段,这不是真的(解决方案2也不起作用)。