C# 为什么不是';t Dispatcher.BeginInvoke在重新创建我的WPF数据网格列时是否工作?

C# 为什么不是';t Dispatcher.BeginInvoke在重新创建我的WPF数据网格列时是否工作?,c#,wpf,multithreading,datagrid,begininvoke,C#,Wpf,Multithreading,Datagrid,Begininvoke,我有一个DataGrid,其中包含一组为一系列日期动态生成的列,并使用自定义依赖属性绑定到网格 public static readonly DependencyProperty BindableColumnsProperty = DependencyProperty.RegisterAttached("BindableColumns", typeof(ObservableCollection<DataGridColumn>), typeof(

我有一个
DataGrid
,其中包含一组为一系列日期动态生成的列,并使用自定义依赖属性绑定到网格

public static readonly DependencyProperty BindableColumnsProperty =
    DependencyProperty.RegisterAttached("BindableColumns",
        typeof(ObservableCollection<DataGridColumn>),
        typeof(DataGridAttachedProperties),
        new UIPropertyMetadata(null, BindableColumnsPropertyChanged));
dataGrid.Columns.Remove(column)
上,从
refresh命令调用我的
InitColumns
方法时,我得到错误:

调用线程无法访问此对象,因为另一个 线程拥有它。 我通过将
Remove
代码更改为:

然后我再试一次,
Remove
代码可以工作,但是我在
dataGrid.Columns.Add(column)
行上显然得到了相同的错误。我还改变了这一点:

Application.Current.Dispatcher.BeginInvoke((Action)(() =>
{
    dataGrid.Columns.Add(column);
}));
我再次尝试运行refresh命令,新调度的
Remove
仍然有效,但是现在我得到了相同的错误,但是使用
dataGrid.Columns.Add(column)
调用的
BeginInvoke
委托:

这我不明白。确实是同一个线程成功地删除了列,但是现在看起来有一些新的虚拟线程正在尝试添加列。可能是什么原因造成的?

试试这段代码

dataGrid.Dispatcher.BeginInvoke((Action)(() =>
{
    dataGrid.Columns.Add(column);
}));
而不是你的代码


我认为您使用的是多个UI线程,因此会出现这种错误

据我所知,您正在从一个非UI线程操作一个绑定到UI的
observeableCollection
。因此,在处理
CollectionChanged
(以及
PropertyChanged
)时使用
Dispatcher
,并将更改应用到
数据网格
是正确的方法

但是为什么
Remove
有效而
Add
无效呢

这篇文章中没有显示,但我怀疑您不仅在向可观察集合添加
DataGridColumn
子体,而且还在非UI线程上创建(并初始化)它们,这将导致所描述的行为

这是因为类继承(通过)了。
DispatcherObject
类(即派生类)将当前线程
Dispatcher
存储在其构造函数中,然后使用该Dispatcher验证是否从创建该对象的线程进行了每个依赖性属性访问

很快,整个
DataGridColumn
的创建和操作应该在UI线程上进行。找到创建列的代码,并确保它通过使用
Application.Current.Dispatcher
和一些
Dispatcher.Invoke
重载在UI线程上执行

注意:尽管以上是我能想到的最符合逻辑的解释,但您可以通过修改发布的代码来验证假设是否正确,如下所示:

Application.Current.Dispatcher.BeginInvoke((Action)(() =>
{
    if (!column.CheckAccess())
    {
        // Houston, we've got a problem!
    }
    dataGrid.Columns.Add(column);
}));

并在
if
语句中放置一个断点。

应用程序。Current.Dispatcher
将获取当前应用程序的
Dispatcher
。因此,在上调用
BeginInvoke
并不一定意味着它会工作:如果不是线程不是控件的所有者,该怎么办?毕竟,任何线程都可以创建控件。因此,您应该尝试获取控件的
调度程序
,然后调用
BeginInvoke

YourControl.Dispatcher.Invoke();
这并不意味着你可以简单地做到这一点:

if (column.CheckAccess())
{
    // Great this thread has access so I will remove this column.
    dataGrid.Columns.Add(column);
}
该检查只告诉您当前线程拥有
,但不一定是
dataGrid
集合(我不太确定,但您有代码,所以您可以简单地尝试)。因此,您需要执行以下操作:

if (column.CheckAccess())
{
    if (dataGrid.CheckAccess())
    {
        // Code here. You may need to check `Columns` as well but like I said I am not sure about this one.
    }
    else
    {
        dataGrid.Dispatcher.BeginInvoke(/* code here */);
    }
}
else
{
    column.Dispatcher.BeginInvoke(/* code here */);
}

现在您看到,这将使您陷入困境,因此,最好在同一线程上创建父ui控件及其子控件。至少这将帮助您解决问题,并且将来您知道不要使用
Application.Current.Dispatcher

因为您在非ui线程上创建了DataGridColumn,所以会出现此错误

dataGrid.Columns.Add(column)时时,内部DataGrid正在尝试获取新项目的DataGridColumn.DisplayIndex
依赖属性值,执行此操作后,DataGridColumn将调用
CheckAccess
,以验证当前线程是在其上创建的线程,并且此测试失败并导致引发异常

所以引发异常的是DataGridColumn

我创建了一个小测试项目来检查这个(),特别是创建了两个只与线程DataGridColumns不同的方法:

这将导致异常(在线程池线程上创建列):

你应该这样做

Application.Current.Dispatcher.BeginInvoke((Action)(() =>
{   
     dataGrid.Columns.Add(CreateColumnFromViewModel(columnVm));
}));
反过来,你会得到

  • 您的视图与ViewModel和ViewModel分离,而ViewModel没有绑定到具体的视图实现
  • 您可以确保始终在UI线程上创建DataGridColumns

  • 如果您感兴趣,我可以对此进行详细说明。简单地说,在UI线程上创建
    DataGridColumn
    实例

    如果您想从async/await中获益,我会选择一种更为MVVM友好的方法:

    • 如果不使用
      observateCollection
      ,我会使用“描述”所需列的模型列表,例如
      observateCollection

    • 其中
      MyColumnDefinition
      包含用于创建
      DataGridColumn
      的属性,例如:映射到列类型、绑定路径、宽度等的值

    • 然后在
      PropertyChanged
      处理程序中执行从
      MyColumnDefinition
      DataGridColumn
      的转换

    • 在创建
      DataGridColumn
      实例并将其添加到
      DataGrid
      Columns
      属性时,仍应使用dispatcher

    这样,您就可以使用以下方法创建列:

    await Task.Run(() => new MyColumnDefinition { ... });
    
    我认为
    await Task.Delay(1).ConfigureAwait(false);    //detaching to thread pool thread
    
    ViewModel.Columns = new ObservableCollection<DataGridColumn>(); //initializing colleciton in thread pool, but this gets marshalled by WPF
    
    Dispatcher.Invoke(() => { }, DispatcherPriority.ApplicationIdle);  //wait for dispatched PropertyChanged to take effect
    
    //creating cols in thread pool thread
    var cols = new List<DataGridColumn>
    {
        new DataGridTextColumn {Header = "test col 1", Binding = new Binding()},
        new DataGridTextColumn {Header = "test col 2", Binding = new Binding()}
    };
    
    //adding previously created columns - should throw
    foreach (var dataGridColumn in cols)
        ViewModel.Columns.Add(dataGridColumn);
    
    //creating cols in UI thread
    var cols = new List<DataGridColumn>
    {
        new DataGridTextColumn {Header = "test col 1", Binding = new Binding()},
        new DataGridTextColumn {Header = "test col 2", Binding = new Binding()}
    };
    
    await Task.Delay(1).ConfigureAwait(false);    //detaching to thread pool thread
    
    ViewModel.Columns = new ObservableCollection<DataGridColumn>(); //initializing colleciton in thread pool
    
    Dispatcher.Invoke(() => { },DispatcherPriority.ApplicationIdle);  //wait for dispatched PropertyChanged to take effect
    
    //adding previously created columns - should succeed
    foreach (var dataGridColumn in cols)
        ViewModel.Columns.Add(dataGridColumn);
    
    Application.Current.Dispatcher.BeginInvoke((Action)(() =>
    {
        dataGrid.Columns.Add(column);
    }));
    
    Application.Current.Dispatcher.BeginInvoke((Action)(() =>
    {   
         dataGrid.Columns.Add(CreateColumnFromViewModel(columnVm));
    }));
    
    await Task.Run(() => new MyColumnDefinition { ... });