从不同线程加载自定义用户控件时出错

时间:2014-04-10 15:30:56

标签: c# wpf multithreading

我有一个WPF项目,从主窗口我创建和加载一些用户控件,有一些大数据我在后台加载然后更新内置控件抛出调度程序,这工作正常,问题是一些用户控件加载了大量数据,例如我在主窗口的主要区域加载的第一件事,我想要的是放置一个加载标签,加载主窗口的速度和可能这样用户看到这个标签并在后台运行该用户控件的创建,并在完成时将其添加为我的主窗口上的主容器区域的子项,同时我删除加载标签,如果我遵循相同的理念我运行进入相同的错误,就像我运行任务然后尝试更新窗口而不使用调度程序。我希望能够异步创建用户控件然后更新主窗口。

代码:

用户控制:

public partial class CustomUserControlGallery : UserControl
{
  public CustomUserControlGallery()
  {
    InitializeComponent();
  }
    ...
}

在主窗口的后端类中:

public partial class MainWindow : Window
{
  CustomUserControlGallery _customUserControlGallery; 

  public MainWindow()
  {
    InitializeComponent();
    Task t = new Task({
    //Can't use the _customUserControlGallery's Dispatcher because object is uninitialized and this.Dispatcher not working either.
      _customUserControlGallery = new CustomUserControlGallery(); //Error Here.
      _gridContainer.Dispatcher.Invoke(new Action(() => _gridContainer.Children.Add(_customUserControlGallery)));
      _loadingLabel.Visbility = Visibility.Collapse;
    });

     t.Start();
   }
   ...
}

我不知道如何使用与用户控件和主线程关联的线程来处理这种情况。

错误:

{"调用线程必须是STA,因为许多UI组件都需要这个。"}

3 个答案:

答案 0 :(得分:2)

你做错了。必须创建所有控件&在UI线程上运行。也就是说,您可以使用BackgroundWorker类来加载数据。

您通常通过禁用其数据在后台加载或隐藏它的控件来执行此操作。在其位置显示进度指示器。然后,您开始BackgroundWorker。这可以传达使用ReportProgress方法的距离。最后,当它完成运行时,会触发RunWorkerCompleted事件,并使用它来启用控件,或者隐藏进度指示器&显示控件。

一些快速&脏的(未经测试的)代码:

将它放在Initialize()或控件构造函数中:

private BackgroundWorker loadData = new BackgroundWorker();

loadData.DoWork += loadData_DoWork;
loadData.ProgressChanged += loadData_ProgressChanged; // Only do this if you are going to report progress
loadData.WorkerReportsProgress = true;
loadData.WorkerSupportsCancellation = false; // You can set this to true if you provide a Cancel button
loadData.RunWorkerCompleted += loadData_RunWorkerCompleted;


private void DoWork( object sender, DoWorkEventArgs e ) {
    BackgroundWorker worker = sender as BackgroundWorker;

    bool done = false;
    while ( !done ) {
        // If you want to check for cancellation, include this if statement
        if ( worker.CancellationPending ) {
            e.Cancel = true;
            return;
        }

        // Your code to load the data goes here.

        // If you wish to display progress updates, compute how far along you are and call ReportProgress here.
    }
}

private void loadData_ProgressChanged( object sender, ProgressChangedEventArgs e ) {
    // You code to report the progress goes here.
}

private void loadData_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e ) {
    // Your code to do whatever is necessary to put the UI into the completed state goes here.
}

答案 1 :(得分:0)

你实际上在说(我认为)是你的应用程序在你的控件呈现大量数据时变得迟钝。

这是一个需要通过虚拟化解决的问题。您无法在后台线程上创建控件,让它在幕后呈现其数据然后将其弹出。您可以在单独的调度程序上创建控件,但它们不能共享相同的可视化和逻辑树,因此您将无法将其作为另一个的子项。

虚拟化是您需要关注的。根据控件的不同,您可以使用各种虚拟化设置。尝试谷歌主题,因为有很多关于如何有效实现这一目标的信息。您很可能希望使用虚拟化堆栈面板和容器回收等内容。

答案 2 :(得分:-1)

您无法使用不同的UI controls创建Dispatchers并相互使用它们。它是不被允许的。您想要做的是Task您在没有UI更新的情况下完成繁重的工作,完成后再将其推回Dispatcher以更新UI

在你的情况下,我甚至不会使用Dispatcher.Invoke。由于您使用的是Task,因此您可以在构造函数中传递TaskScheduler.FromCurrentSynchronizationContext()

如果您要将controls发送给Main调度员,那么在另一个主题中实例化{{1}}的目的是什么?这样做并不昂贵。