【问题标题】:XamlReader.Load in a Background Thread. Is it possible?XamlReader.Load 在后台线程中。是否可以?
【发布时间】:2011-03-22 17:20:07
【问题描述】:

WPF 应用有一个使用XamlReader.Load() 方法从单独的文件加载用户控件的操作:

StreamReader mysr = new StreamReader(pathToFile);
DependencyObject rootObject = XamlReader.Load(mysr.BaseStream) as DependencyObject;
ContentControl displayPage = FindName("displayContentControl") as ContentControl;
displayPage.Content = rootObject;

由于文件的大小,该过程需要一些时间,因此 UI 会冻结几秒钟。

为了保持应用响应,我尝试使用后台线程来执行不直接参与 UI 更新的部分操作。

尝试使用BackgroundWorker时出现错误:调用线程必须是STA,因为很多UI组件都需要这个

所以,我走了另一条路:

 private Thread _backgroundThread;
 _backgroundThread = new Thread(DoReadFile);
 _backgroundThread.SetApartmentState(ApartmentState.STA);
 _backgroundThread.Start();
 void DoReadFile()
 {
   StreamReader mysr3 = new StreamReader(path2);
   Dispatcher.BeginInvoke(
           DispatcherPriority.Normal,
           (Action<StreamReader>)FinishedReading,
           mysr3);
 }

 void FinishedReading(StreamReader stream)
    {            
        DependencyObject rootObject = XamlReader.Load(stream.BaseStream) as DependencyObject;
        ContentControl displayPage = FindName("displayContentControl") as ContentControl;
        displayPage.Content = rootObject;
    }

这没有解决任何问题,因为所有耗时的操作都保留在 UI 线程中。

当我这样尝试时,在后台进行所有解析:

private Thread _backgroundThread;
_backgroundThread = new Thread(DoReadFile);
_backgroundThread.SetApartmentState(ApartmentState.STA);
_backgroundThread.Start();
 void DoReadFile()
 {
  StreamReader mysr3 = new StreamReader(path2);      
  DependencyObject rootObject3 = XamlReader.Load(mysr3.BaseStream) as DependencyObject;
        Dispatcher.BeginInvoke(
           DispatcherPriority.Normal,
           (Action<DependencyObject>)FinishedReading,
           rootObject3);
  }

  void FinishedReading(DependencyObject rootObject)
  {            
    ContentControl displayPage = FindName("displayContentControl") as ContentControl;
    displayPage.Content = rootObject;
  } 

我遇到了一个异常:调用线程无法访问该对象,因为它拥有不同的线程。(在加载的 UserControl 中存在其他控件,它们可能会给错误)

有什么方法可以让 UI 响应式地执行此操作?

【问题讨论】:

  • 使用后台工作者,确保如果你要修改(设置/添加)任何不在范围内的对象,而不是后台工作者所在的线程,你使用 func/可以解决的操作或委托,不要尝试在后台工作线程中设置它。如果有任何事情代替后台工作人员完成您的工作,完成后获取 OnComplete 事件/方法中的结果(e.result)并在 UI 线程中更新您的对象。

标签: c# wpf multithreading xamlreader


【解决方案1】:

让 XAML 加载后台线程本质上是不可能的。 WPF 组件具有线程关联性,并且通常只能从它们创建的线程中使用。因此,在后台线程上加载将使 UI 响应,但会创建无法插入 UI 线程的组件。

这里最好的选择是将 XAML 文件分解成更小的部分,并在 UI 线程中逐步加载它们,确保在每次加载操作之间允许消息泵。可能在Dispatcher 对象上使用BeginInvoke 来安排负载。

【讨论】:

    【解决方案2】:

    正如您所发现的,除非线程是 STA,否则您不能使用 XamlReader.Load,即使它是,您也必须让它启动一个消息泵并汇集对其通过该线程创建的控件的所有访问权限。这是 WPF 工作原理的基本方式,您不能违背它。

    所以你唯一真正的选择是:

    1. 将 XAML 分解成更小的部分。
    2. 为每个 Load 调用启动一个新的 STA 线程。在Load 返回后,线程将需要启动一个消息循环并管理它创建的控件。您的应用程序必须考虑到不同的控件现在由不同的线程拥有这一事实。

    【讨论】:

      【解决方案3】:
      【解决方案4】:

      System.Xaml 有一个Xaml​Background​Reader 类,也许你可以让它为你工作。在后台线程上解析 XAML,但在 UI 线程上构建对象。

      【讨论】:

        【解决方案5】:

        您可以调用允许将控制权交给另一个线程的方法:

        http://msdn.microsoft.com/en-us/library/ms748331.aspx

        它被称为.Freeze()

        【讨论】:

          猜你喜欢
          • 2020-08-26
          • 1970-01-01
          • 1970-01-01
          • 2023-03-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-05-18
          • 1970-01-01
          相关资源
          最近更新 更多