使用后台工作程序动态更新DataGrid



我有一个自定义类的ObservableCollection,它包含一个字符串和一个int:

public class SearchFile
{
public string path { set; get; }
public int occurrences { set; get; }
}

我想在dataGrid中显示集合。该集合有一些方法,每当更新时都会发出通知,到目前为止,只需要将其链接到DataGrid.ItemsSource(正确吗?)。以下是网格XAML(C#代码后台有dataGrid1.ItemsSource = files;):

<DataGrid AutoGenerateColumns="False" Height="260" Name="dataGrid1" VerticalAlignment="Stretch" IsReadOnly="True" ItemsSource="{Binding}" >
<DataGrid.Columns>
<DataGridTextColumn Header="path" Binding="{Binding path}" />
<DataGridTextColumn Header="#" Binding="{Binding occurrences}" />
</DataGrid.Columns>
</DataGrid>

现在情况更复杂了。我首先想显示paths,其中occurrence的默认值为零。然后,我想遍历每个SearchFile,并用计算值occurrence更新它。这是辅助功能:

public static void AddOccurrences(this ObservableCollection<SearchFile> collection, string path, int occurrences)
{
for(int i = 0; i < collection.Count; i++)
{
if(collection[i].path == path)
{
collection[i].occurrences = occurrences;
break;
}
}
}

这是占位符辅助函数:

public static bool searchFile(string path, out int occurences)
{
Thread.Sleep(1000);
occurences = 1;
return true; //for other things; ignore here     
}

我使用BackgroundWorker作为背景线程。方法如下:

private void worker_DoWork(object sender, DoWorkEventArgs e)
{           
List<string> allFiles = new List<string>();
//allFiles = some basic directory searching
this.Dispatcher.Invoke(new Action(delegate
{
searchProgressBar.Maximum = allFiles.Count;
files.Clear(); // remove the previous file list to build new one from scratch
}));
/* Create a new list of files with the default occurrences value. */
foreach(var file in allFiles)
{
SearchFile sf = new SearchFile() { path=file, occurrences=0 };
this.Dispatcher.Invoke(new Action(delegate
{
files.Add(sf);
}));
}
/* Add the occurrences. */
foreach(var file in allFiles)
{
++progress; // advance the progress bar
this.Dispatcher.Invoke(new Action(delegate
{
searchProgressBar.Value = progress;
}));
int occurences;
bool result = FileSearcher.searchFile(file, out occurences);
files.AddOccurrences(file, occurences);
}
}

现在,当我运行它时,有两个问题。首先,更新进度条的值会引发The calling thread cannot access this object because a different thread owns it.异常。为什么?它在调度员那里,所以应该可以正常工作。第二,foreach环路在bool result =...线上断开。我对它进行了注释,并尝试设置int occurences = 1,然后循环继续进行,但有一些奇怪的事情发生了:每当我调用该方法时,它要么是全零,要么是一个介于状态之间的状态,onez在一个看似随机的零数后开始)。

为什么?

Dispatcher并不像听起来那么简单。使用调度器意味着代码将在创建包含对象的同一线程上执行,但不一定是UI线程。确保worker_DoWork方法是在屏幕上显示的实际UI元素中定义的(这只是消除后台线程创建的元素的一种简单方法)。

真的,看看你的代码,我不知道你为什么要使用后台工作者。这看起来实际上会更慢,因为对UI线程的调度是恒定的。相反,我认为更好的选择是将长时间运行的部分放在任务中,并在一次回调中更新UI。例如,在您的代码中,看起来唯一对UI线程来说太慢的可能是FileSearcher调用。这很容易放在后台任务中,返回找到的结果数量。

至于FileSearcher上的问题,您的方法定义不匹配。您发布的方法只使用一个路径和out int,但当您调用它时,您传递了4个参数。如果没有看到你实际呼叫的过载,很难猜测出了什么问题。

编辑:让我再支持一下。您的根本问题只是WPF不支持绑定到从后台线程修改的集合。这就是所有调度和其他复杂代码的原因。我最初的建议(长期工作的单一任务)是解决这个问题的一种方法。但是使用ObservableList类可能会做得更好。这使您可以从后台线程更新集合,并自动通知UI,而无需使用调度器。这可能会使大多数复杂的线程问题消失。我建议你通读前三篇文章,以供参考,那里有很多好的信息。

最新更新