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)时执行code>时,内部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 { ... });