Brush 类型的绑定 属性 在异步调用时抛出异常

Binding property of type Brush throws exception on asynchronous call

我正在将一个可观察集合绑定到一个通过异步调用从服务器获取的数据 Grid.The 集合。集合模型包含类型为 'System.Windows.Media.Brush' 的名为 'BackgroundBrush' 的 属性,它绑定到数据网格中模板列的背景颜色。画笔 属性 可以是 SolidColorBrush 或 LinearGradientBrush,具体取决于应用于 属性 的业务逻辑。

在向数据网格呈现数据时,应用程序抛出这样的异常"Must create DependencySource on same Thread as the DependencyObject."

调试问题时注意到的事情

  1. 问题出在 'Background' 属性。注释掉此 属性 绑定并使异步调用工作正常。

  2. 使服务调用同步工作正常,但我需要它作为异步调用。

  3. 在Application.Current.Dispatcher.Invoke中进行服务调用没有任何区别

下面是示例应用程序代码

型号

public class Model
{
    public string Name { get; set; }

    public string Email { get; set; }

    public string Address { get; set; }

    public Brush BackgroundBrush { get; set; }
}

查看模型

private ObservableCollection<Model> _dataCollection;

public ObservableCollection<Model> DataCollection
{
    get { return _dataCollection; }
    set
    {
        _dataCollection = value;
        RaisePropertyChanged(() => DataCollection);
    }
}

public RelayCommand LoadCommand { get; private set; }

private async Task LoadData()
{
    var list = await Task.Run(() => GetData());
    DataCollection = new ObservableCollection<Model>(list);

}

private ObservableCollection<Model> GetData()
{
    return new ObservableCollection<Model>()
    {
        new Model()
        {
            Address = "a",
            Email = "2",
            Name = "3",
            BackgroundBrush = new SolidColorBrush(Colors.SaddleBrown)
        }
    };
}

查看

<Grid x:Name="LayoutGrid">
    <DataGrid ItemsSource="{Binding DataCollection}" AutoGenerateColumns="False">
        <DataGrid.Columns>
            <DataGridTemplateColumn>
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                    <Border Background="{Binding BackgroundBrush}">
                        <TextBlock Text="{Binding Name}"></TextBlock>
                        </Border>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>
</Grid>

那是因为 await Task.Run 中的所有内容都在不同的线程上运行。所以你不能改变其中的绑定源DataCollection。由于 ObservableCollection 触发事件来更新绑定目标,因此它不允许来自不同线程的更改。

所以你的 GetData 函数应该 return 一个正常的集合:

private IEnumerable<Model> GetData()
{
    return new List<Model>()
    {
         new Model(){...}
    };
}

另一个问题是Brush。您的模型(线程安全)应使用 Color,您的 viewModel(可绑定)应使用 Brush。所以你应该在你的模型中添加一个 Color 属性 ,它可以在 GetData 的异步调用中设置。像这样:

private Color _color;
public Color Color 
{ 
    get{ return _color; } 
    set
    { 
        _color = value; 
        Dispatcher.Invoke(()=>Brush = new SolidColorBrush(Color)); 
    } 
}
public Brush Brush { get; set; }

或者您不绑定到模型并遵循标准的 MVVM 模式。

对于涉及依赖属性或任何绑定源的部分,您需要从调度程序调用。例如

Dispatcher.Invoke(() => aThreadSafeFunction());

但是在这种情况下,您可以先读取整个列表,然后将其转换为 ObservableCollection:

private async Task LoadData()
{
    //load thread-safe data asynchronously
    var list = await Task.Run(() => GetData());
    //set binding source in the same thread
    DataCollection = new ObservableCollection<Model>(list);
}

System.Media.Brush 是一个 DependencyObject,因此需要在 Dispatcher 线程上创建。

ObservableCollection 通过 INotifyCollectionChanged 使用对 UI 的通知来启用它的 Observer 模式实现,这意味着它也需要在 Dispatcher 上构建线。如果不编写自定义实现以在正确的线程上引发通知,则无法从另一个线程(即异步)加载 ObservableCollection 的内容。

编辑:

要解决您的问题 - 在构造函数中创建 ObservableCollection 并且永远不要覆盖来自其他线程的 属性 引用。

LoadData()GetData()如下:

    private async Task LoadData()
    {
        var list = await Task.Run(() => GetData());
        list.ForEach(item => Dispatcher.Invoke(() => 
        {
           DataCollection.Add(item);
        }));

    }

    private List<Model> GetData()
    {
        var modelObject =
            new Model()
            {
                Address = "a",
                Email = "2",
                Name = "3",
            };
        Dispatcher.Invoke(() => 
        { 
            modelObject.BackgroundBrush = new SolidColorBrush(Colors.SaddleBrown);
        });

       return new List<Model>(){ modelObject };
    }