为什么这段代码会抛出异常?我认为 RunWorkerCompleted 可以访问 UI 个元素
Why does this code throw an exception? I thought RunWorkerCompleted could access UI elements
我的 WPF 应用程序中有一个数据网格,当应用程序启动时,我从数据库中获取部署记录并将它们加载到绑定到数据网格的 ObservableCollection 中。
通过计时器,我使用 BackgroundWorker 从数据库中获取任何新记录并将它们放入新的 ObservableCollection 中。
然后,通过 RunWorkerCompleted,我尝试使用新 ObservableCollection 中的项目更新数据网格。
但是,我收到 'System.NotSupportedException'。
Additional information: This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.
我以为我可以从 RunWorkerCompleted 方法访问 UI 控件,但似乎发生了一些我不明白的事情。
My code is below:
private void MetroWindow_Loaded(object sender, RoutedEventArgs e)
{
//Init the deployment collection
deployments = DataAccess.GetDeployments();
dgDeployments.ItemsSource = deployments;
//Setup the background worker
bw = new BackgroundWorker();
bw.DoWork += bw_DoWork;
bw.RunWorkerCompleted += bw_RunWorkerCompleted;
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
//Setup timer
System.Threading.Timer t = new System.Threading.Timer(new System.Threading.TimerCallback(TimerProc));
t.Change(10000, 0);
}
private void TimerProc(object state)
{
//MessageBox.Show("Timer fired!");
//Get the latest currentTime from the items in the grid
DateTime? latestTime = DataAccess.GetDeployments().ToList()[0].CurrentTime;
bw.RunWorkerAsync(latestTime);
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
//Get new records that have changed since that time
e.Result = (ObservableCollection<Deployment>)DataAccess.GetDeployments((DateTime)e.Argument);
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if(e.Error != null)
{
MessageBox.Show(e.Error.Message);
}
else if (e.Cancelled)
{
MessageBox.Show("The operation was cancelled");
}
else
{
ObservableCollection<Deployment> newDeployments = (ObservableCollection<Deployment>)e.Result;
foreach (Deployment d in newDeployments)
{
//Remove this new/changed deployment from the collection bound to the datagrid
int index = deployments.IndexOf(deployments.Where(x => x.UniqueID == d.UniqueID).FirstOrDefault());
if (index > -1)
{
deployments.RemoveAt(index);
}
//Now add the new deployments
deployments.Add(d);
deployments.Move(deployments.IndexOf(d), 0);
}
}
}
计时器进程starts
不同线程中的后台工作者。尝试在 GUI 线程中启动后台工作程序,这样完成的操作将 运行 在 GUI 线程上而不是从计时器启动的线程。
要么 Invoke
对 GUI 线程的操作,要么只使计时器成为 DispatchTimer
。
谢谢大家提供的好资料。它帮助我更好地理解了这个问题。这是我使用的代码,最终对我有用。
Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate()
{
//Remove the old deployment
deployments.RemoveAt(index);
//Now add the new deployments
deployments.Add(d);
deployments.Move(deployments.IndexOf(d), 0);
});
我的 WPF 应用程序中有一个数据网格,当应用程序启动时,我从数据库中获取部署记录并将它们加载到绑定到数据网格的 ObservableCollection 中。
通过计时器,我使用 BackgroundWorker 从数据库中获取任何新记录并将它们放入新的 ObservableCollection 中。
然后,通过 RunWorkerCompleted,我尝试使用新 ObservableCollection 中的项目更新数据网格。
但是,我收到 'System.NotSupportedException'。
Additional information: This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.
我以为我可以从 RunWorkerCompleted 方法访问 UI 控件,但似乎发生了一些我不明白的事情。
My code is below:
private void MetroWindow_Loaded(object sender, RoutedEventArgs e)
{
//Init the deployment collection
deployments = DataAccess.GetDeployments();
dgDeployments.ItemsSource = deployments;
//Setup the background worker
bw = new BackgroundWorker();
bw.DoWork += bw_DoWork;
bw.RunWorkerCompleted += bw_RunWorkerCompleted;
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
//Setup timer
System.Threading.Timer t = new System.Threading.Timer(new System.Threading.TimerCallback(TimerProc));
t.Change(10000, 0);
}
private void TimerProc(object state)
{
//MessageBox.Show("Timer fired!");
//Get the latest currentTime from the items in the grid
DateTime? latestTime = DataAccess.GetDeployments().ToList()[0].CurrentTime;
bw.RunWorkerAsync(latestTime);
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
//Get new records that have changed since that time
e.Result = (ObservableCollection<Deployment>)DataAccess.GetDeployments((DateTime)e.Argument);
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if(e.Error != null)
{
MessageBox.Show(e.Error.Message);
}
else if (e.Cancelled)
{
MessageBox.Show("The operation was cancelled");
}
else
{
ObservableCollection<Deployment> newDeployments = (ObservableCollection<Deployment>)e.Result;
foreach (Deployment d in newDeployments)
{
//Remove this new/changed deployment from the collection bound to the datagrid
int index = deployments.IndexOf(deployments.Where(x => x.UniqueID == d.UniqueID).FirstOrDefault());
if (index > -1)
{
deployments.RemoveAt(index);
}
//Now add the new deployments
deployments.Add(d);
deployments.Move(deployments.IndexOf(d), 0);
}
}
}
计时器进程starts
不同线程中的后台工作者。尝试在 GUI 线程中启动后台工作程序,这样完成的操作将 运行 在 GUI 线程上而不是从计时器启动的线程。
要么 Invoke
对 GUI 线程的操作,要么只使计时器成为 DispatchTimer
。
谢谢大家提供的好资料。它帮助我更好地理解了这个问题。这是我使用的代码,最终对我有用。
Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate()
{
//Remove the old deployment
deployments.RemoveAt(index);
//Now add the new deployments
deployments.Add(d);
deployments.Move(deployments.IndexOf(d), 0);
});