XamlReader.Load in a Background Thread. Is it possible?
Asked Answered
J

5

9

A WPF app has an operation of loading a user control from a separate file using XamlReader.Load() method:

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

The process takes some time due to the size of the file, so UI becomes frozen for several seconds.

For keeping the app responsive I try to use a Background thread for performing the part of operation that is not directly involed in UI updating.

When trying to use BackgroundWorker I got an error: The calling thread must be STA, because many UI components require this

So, I went another way:

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

This solves nothing because all time consuming operations remain in UI thread.

When I try like this, making all parsing in the background:

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

I got an exception: The calling thread cannot access this object because a different thread owns it. (in the loaded UserControl there are other controls present which maybe give the error)

Is there any way to perform this operation in such a way the UI to be responsive?

Judiejudith answered 22/3, 2011 at 17:20 Comment(1)
Use the backgroundworker, make sure that if you are going to modify(set/add) any of the objects that aren't in scope instead of the thread the backgroundworker is on that you use a func/action or delegate that work out and don't try and set it in the backgroundworker thread. If anything do your work instead the backgroundworker and when you are done take the result(e.result) in the OnComplete event/method and update your object in the UI Thread.Azral
G
7

Getting the XAML to load an a background thread is essentially a non-starter. WPF components have thread affinity and are generally speaking only usable from the threads they are created one. So loading on a background thread will make the UI responsive but create components which then cannot be plugged into the UI thread.

The best option you have here is to break up the XAML file into smaller pieces and incrementally load them in the UI thread making sure to allow for a message pump in between every load operation. Possibly using BeginInvoke on the Dispatcher object to schedule the loads.

Goering answered 22/3, 2011 at 17:25 Comment(0)
L
2

As you found out, you can't use XamlReader.Load unless the thread is STA and even if it is, you will have to have it start a message pump and funnel all access to the controls it created through that. This is a fundamental way of how WPF works and you can't go against it.

So your only real options are:

  1. Break down the XAML into smaller pieces.
  2. Start a new STA thread for each Load call. After Load returns, the thread will need to start a message loop and manage the controls it created. Your application will have to take into account the fact that different controls are now owned by different threads.
Lunitidal answered 22/3, 2011 at 17:29 Comment(0)
C
0

System.Xaml has a Xaml​Background​Reader class, perhaps you could get that to work for you. Parser the XAML on the background thread but build the objects on the UI thread.

Credenza answered 29/4, 2017 at 4:0 Comment(0)
U
-1

You can call a method that allows to give control to another thread:

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

It is called .Freeze()

Underneath answered 1/8, 2011 at 15:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.