从非UI线程访问UI元素

时间:2014-01-14 13:31:58

标签: c# wpf mvvm

我有一个小的 WPF / MVVM 示例项目,包含两个可视元素(一个ComboBox和一个简单的TextBlock)。这两个元素都绑定到我的ViewModel的属性:

属性MainViewModel.cs

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);
    }
}

绑定MainWindow.xaml

<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按预期工作?

2 个答案:

答案 0 :(得分:2)

  

现在我从非UI线程中更改这两个属性(就是这样)   据我所知,不允许)

一般情况下,无论您使用什么线程,更改属性值都非常精细。当更改属性具有“有趣”的副作用时,问题和限制可能会出现

在这种情况下,两个被更改的属性都会产生有趣的副作用,并且观察到的行为的差异是由于这些副作用(来自框架代码,您无法直接看到)以不同的方式处理。 / p>

  

为什么解决方案1抛出NotSupportedExpection(不允许)   解决方案2工作时,从非调度程序线程更改集合   根据需要?

当更改绑定源的属性时,WPF绑定系统通过对UI进行相应的更新来响应;但是,当从后台线程进行更改时,绑定系统的事件处理程序也将在后台线程中运行,并且无法直接更新UI。对于这里涉及的两种情况都是如此。

不同之处在于,对于“简单”属性值更改,绑定系统会自动检测到它没有响应UI线程上的更改,并使用Dispatcher.Invoke将UI更改分派给正确的线程,但是当可观察时集合被修改此调度不会自动发生。结果是更新UI的代码在后台线程上运行并抛出异常。

解决方案

有两件事可以解决这个问题:

  1. 直接在UI线程中更改属性

    如果在UI线程上进行了更改,那么任何PropertyChanged处理程序也将在UI线程上运行,因此他们可以自由地进行他们想要的任何UI更改。此解决方案可以在您自己的代码中强制执行,并且永远不会导致问题,但如果不需要更改UI,则无需任何好处即可完成调度到UI线程的额外工作。

  2. 确保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也不起作用)。