【问题标题】:WPF How to update GUI when background thread finishes creating collections?WPF如何在后台线程完成创建集合时更新GUI?
【发布时间】:2014-03-11 01:35:48
【问题描述】:

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

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

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

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

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

这会因错误而崩溃: “调用线程无法访问此对象,因为另一个线程拥有它。”

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

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

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

它不会崩溃(但它也不会显示我的数据)。任何想法可能导致崩溃?

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

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

完整代码:

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;
}

【问题讨论】:

  • 您将this 传递给解析器。我认为我们需要看看该代码中发生了什么。可以发一下吗?
  • 我很确定我找到了真正的问题。我试图在可观察的集合上调用 OnPropertyChanged。我猜我试图处理我的集合视图源而不是通过强制它来更新。但我不明白为什么它适用于一个案例而不会崩溃。无论如何,现在我只需要弄清楚我实际上应该做什么。一种选择是将新创建的 Observable 集合重新绑定到我在 xaml 中定义的 collectionviewsource。或者我可以将集合传递给后台线程,但让任何视图模型更新都由 WPF 调度程序调度。我敢打赌前者更容易
  • 为什么不让解析器返回一个List,然后在返回时在UI线程中添加一个新的ObservableCollection?是否需要是后台线程中的 ObservableCollection?
  • 不,我会试试看是否有帮助。我认为问题可能出在我的 collectionviewsource 上。是否无法更新 UI 正在使用的 collectionviewsource 的源?
  • 没有理由不为 TopLevelModules 对象本身调用 NotifyPropertyChanged - 这应该会触发 UI 重新加载整个集合而没有问题。

标签: c# wpf backgroundworker


【解决方案1】:

我认为使用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。]

【讨论】:

  • 这是个好主意,但我使用的是 .net 3.5 atm
【解决方案2】:

您很可能在 UI 线程以外的线程上创建、读取或修改 ObservableCollection。还要确保您没有在 UI 线程以外的任何地方添加或删除 ObservableCollection。

要对此进行调试,请在您访问/修改可观察集合的任何位置放置一个断点,并记下命中该断点的线程号(VS 中的线程窗口)。它应该始终相同。

您可以使用另一个结构(列表/数组)来保存结果,然后回调 UI 线程以更新/创建 ObservableCollection。更新 ObservableCollection 的成本很低,即使对于数百个项目也是如此。

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

如果您要添加/删除大量项目,最好创建一个新集合并重新分配 DataSource。如果你总是这样做,你可以使用 List 来代替。 ObservableCollection 用于当您想要修改列表并让控件仅更改尽可能少的数量时。更改数据源(例如更改为列表)将清除控件并重建它,这对于许多更改可能会更好。

见:

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?

【讨论】:

  • 等一下,我很困惑。所以即使 observable 集合还没有绑定到 UI 线程,我不能从非 UI 线程访问它?所以我必须在后台线程中创建一个列表,然后在 UI 线程中从该列表创建一个可观察的集合?如果列表真的很大,这似乎超级低效。如果我必须首先在 UI 线程中创建数据结构,那么在后台运行有什么意义?如果是这种情况,那么我最好不要使用可观察的集合
  • 如果我理解正确的话,我想我就不会担心这个数据结构的通知了。问题是树视图仅用于显示项目的名称属性,在初始加载后永远不会更改,所以我真的不需要树视图的属性更改通知。
  • @JamesJoshuaStreet 我建议您在后台线程中创建一个 List 来保存所有数据,然后切换回 UI 线程并更新 observable。我会用更多细节更新答案。
  • uggh,实际上我刚刚意识到,我有包含可观察集合的可观察项目集合。这意味着我唯一真正的解决方案是使用调度程序添加到可观察的集合中。让后台事物加载事物只是为了使用调度程序创建对象似乎很愚蠢。我不得不承认我很担心。调度员是否保证按照我排队的顺序处理项目?
  • 后台线程加载事物* 所有这些麻烦只是因为我发现可观察集合在单线程情况下更容易使用。我现在正在尝试使用: Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => ParentViewModel.Submodules.Add(NewModuleViewModule) ));我修改可观察集合的任何地方
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多