WPF如何在后台线程完成创建集合时更新GUI?

时间:2014-03-11 01:35:48

标签: c# wpf backgroundworker

所以我想在后台线程中加载我的数据集合,然后将我的树视图绑定到新集合(而不是让每次想要将项目添加到列表时后台线程在调度程序上排队等待[声音]低效])。

这可能吗?我创建了新的数据结构,并在后台线程上以pd.result的形式输出。当UI线程检查对话框已关闭时,应该设置

ModuleHierarchyVM.TopLevelModules = pd.Result as ObservableCollection<ModuleViewModel>;

之后调用事件OnLoadVCD。我有一个事件处理程序,然后尝试设置 树视图的项目源到新集合。

this.AvailableModulesTreeView.ItemsSource = gvvm.ModuleHierarchyVM.TopLevelModules;

这与错误崩溃: &#34;调用线程无法访问此对象,因为另一个线程拥有它。&#34;

甚至不确定如何调试它,调用堆栈也没有提供任何真实的细节。

但是,如果我只是将Itemsource设置为空白的新空集合,如下所示:

this.AvailableModulesTreeView.ItemsSource = (IEnumerable<object>)new List<object>();

它没有崩溃(但它也没有显示我的数据)。什么可能导致崩溃的想法?

我认为可能是因为我正在从错误的线程更新UI,所以我尝试使用begininvoke调用调度程序,并使用dispatcher.checkaccess()检查我确实是UI线程。所以这似乎不是问题。但是,我真的不知道发生了什么。

我可以实现这一点的另一种方法是让我的解析例程只是通过在每个项目上添加到observable集合时调度调度程序来更新绑定到树视图的原始数据结构。然而,即使这是唯一的解决方案,我真的不喜欢不知道为什么某些东西不起作用。在我看来,在不同的线程上创建一个全新的数据结构似乎是合理的,然后将新的数据结构重新绑定到树视图,丢弃旧的数据结构。在解析后台线程上的文件时,对于调度程序放置了几十行ObservableCollectionInstance.Add调用,这对我来说似乎更清晰。

完整代码:

UI线程调用的

方法

public bool LoadPortInterface(string VCDFileName)
{
    ProgressDialog pd = new ProgressDialog("Loading File: ", VCDFileName);
    pd.Owner = Application.Current.MainWindow;
    pd.WindowStartupLocation = WindowStartupLocation.CenterOwner;
    ModuleHierarchyVM.TopLevelModules.Clear();


    VCDData TempVCDOutput = null;
    Func<object> handler = delegate
    {
        return VCDParser.ParseVCDFileForAllPorts(VCDFileName, this, pd.Worker, out TempVCDOutput);
    };
    pd.RunWorkerThread(handler);
    pd.ShowDialog();
    if (pd.DialogResult == true)
    {
        ModuleHierarchyVM.TopLevelModules = pd.Result as ObservableCollection<ModuleViewModel>;
        VCDOutput = TempVCDOutput;
    }
    OnLoadVcd();
}

对graphviewer中的OnLoadVCD事件处理程序的响应:

void gvvm_LoadVCDEvent(object sender, EventArgs e)
{
    this.AvailableModulesTreeView.ItemsSource = gvvm.ModuleHierarchyVM.TopLevelModules;
}

2 个答案:

答案 0 :(得分:2)

我认为使用TPL“任务并行库”会更容易

例如,如果要创建新线程,可以将其创建为任务。

var task = Task.Factory.StartNew(() =>
{
   // write what you want to do here
}

您可以从此链接获取更多示例Task Parallelism (Task Parallel Library)

因此,要从线程更新UI,您可以在与以下相同的UI线程中运行此线程:

        var uiContext = TaskScheduler.FromCurrentSynchronizationContext();

        _taskFactoryWrapper.StartTask(() => DoSomeWork(viewSettings.AnyValue)).ContinueWith(task =>
        {

                viewSettings.Result= task.Result;

        },TaskContinuationOptions.AttachedToParent)
            .ContinueWith(t => EndingUploadingProgress(viewSettings), uiContext);

因此,您可以创建与当前UI线程关联的TaskScheduler。]

答案 1 :(得分:1)

您最有可能在UI线程以外的线程上创建,读取或修改ObservableCollection。另外,请确保您没有在UI线程以外的任何内容上添加或删除ObservableCollection。

要调试此操作,请在访问/修改可观察集合的任何位置放置断点,并记下命中该断点的线程编号(VS中的线程窗口)。它应该总是一样的。

您可以使用另一个结构(List / Array)来保存结果,然后回调UI线程来更新/创建ObservableCollection。即使对于数百个项目,更新ObservableCollection也很便宜。

价格昂贵的是ObservableCollection将在每次更改时引发更改事件,这可能由UI组件处理以更改其布局,这必须在UI线程上完成。此UI事件处理是ObservableCollection阻止您跨线程修改的原因。

如果要添加/删除大量项目,最好创建一个新集合并重新分配DataSource。如果您始终这样做,则可以使用List。 ObservableCollection用于何时修改列表并使控件仅更改可能的最小量。更改DataSource(例如更改为List)将清除控件并重建它,这对于许多更改可能更好。

请参阅:

Updating an ObservableCollection in a separate thread

How do I update an ObservableCollection via a worker thread?

What's the best way to update an ObservableCollection from another thread?