Passing Wpf Objects Between Threads (With Source Code)

When working on yaTImer's new report engine I got myself into a bit of a problem, I'm blogging about it because I couldn't find an answer on the web and I can't believe I'm the only one with this problem, so I hope someone will find my solution helpful (or maybe suggest a better one).

My reports engine generates a FixedDocument that I can print or show to the user, because the report can contain a lot of information generating the document can potentially take some time, so I went for the easy solution and dropped a BackgroundWorker into the code.

So this:

void Window_Loaded(object sender, RoutedEventArgs e)
{
   _documentViewer.Document = GenerateDocument();
}

Becomes this:

// WARNING: THIS CODE DOESN'T WORK
private BackgroundWorker _backgroundWorker;
private FixedDocument _result;

void Window_Loaded(object sender, RoutedEventArgs e)
{
   _backgroundWorker = new BackgroundWorker();
   _backgroundWorker.DoWork += DoGenerateDocument;
   _backgroundWorker.RunWorkerCompleted += FinishedGenerating;
   _backgroundWorker.RunWorkerAsync();
}

void DoGenerateDocument(object sender, DoWorkEventArgs e)
{
   _result = GenerateDocument(); // this line throws an exception
}

void FinishedGenerating(object sender, RunWorkerCompletedEventArgs e)
{
   _documentViewer.Document = _result;
}

While the code was very elegant it doesn't work, I got an exception with a very nice error message: " The calling thread must be STA, because many UI components require this."

Fortunately the error message is very clear and it's easy to find the solution on the web, you can't use Wpf objects from a thread-pool thread (or from a BackgroundWorker) you have to create your own thread, the code to create the thread is very simple:

Thread _backgroundThread;
_backgroundThread = new Thread(DoGenerateDocument);
_backgroundThread.SetApartmentState(ApartmentState.STA);
_backgroundThread.Start();

Now because we no longer have BackgroundWorker events we have to write our own code to pass data between threads, this is easy to do with the Dispacher class

// WARNING: THIS CODE DOESN'T WORK
private Thread _backgroundThread;

void Window_Loaded(object sender, RoutedEventArgs e)
{
   _backgroundThread = new Thread(DoGenerateDocument);
   _backgroundThread.SetApartmentState(ApartmentState.STA);
   _backgroundThread.Start();
}

void DoGenerateDocument()
{
   FixedDocument result = GenerateDocument();
   Dispatcher.BeginInvoke(
      DispatcherPriority.Normal,
      (Action<FixedDocument>)FinishedGenerating,
      result);
}

void FinishedGenerating(FixedDocument result)
{
   _documentViewer.Document = result; // now this line throws an exception
}
This code is written inside a Wpf window, and the Window class has a Dispacher property that returns the dispatcher for the thread that created the window, if you're code doesn't use a window then you need to get the dispatcher associated with the thread you want to communicate with, this is done by reading Dispacher.CurrentDispacher from that thread.

But this code also doesn't work, when I pass the FixedDocument into the document viewer I get this lovely exception: "The calling thread cannot access this object because a different thread owns it."

This is where things get complicated, I couldn't find any way to marshal the document into the UI thread.

Since this is a fixed document the natural thing to do is to save it into an XPS file (XPS is the Wpf version of PDF) and then read it from the other thread, I've tried doing this with a MemoryStream (I don't want to create an actual file) and I discovered a problem with this approach – apparently you can't read an XPS from a memory stream, you need an actual file.

At that point I got the clever idea of using XAML, you can read XAML from a memory stream and the code to read and write XAML is actually simpler then the XPS code, here is the final code using XamlReader and XamlWriter:

private Thread _backgroundThread;

void Window_Loaded(object sender, RoutedEventArgs e)
{
   _backgroundThread = new Thread(DoGenerateDocument);
   _backgroundThread.SetApartmentState(ApartmentState.STA);
   _backgroundThread.Start();
}

void DoGenerateDocument()
{
   FixedDocument result = GenerateDocument();
   MemoryStream stream = new MemoryStream();
   XamlWriter.Save(result, stream);
   Dispatcher.BeginInvoke(
      DispatcherPriority.Normal,
      (Action<MemoryStream>)FinishedGenerating,
      stream);
}

void FinishedGenerating(MemoryStream stream)
{
   stream.Seek(0, SeekOrigin.Begin);
   FixedDocument result = (FixedDocument)XamlReader.Load(stream);
   _documentViewer.Document = result;
}

This trick should use with any Wpf object, not just FixedDocument.

posted @ Wednesday, August 1, 2007 3:45 PM

Comments on this entry:

# re: Passing Wpf Objects Between Threads (With Source Code)

Left by webber at 4/8/2008 8:19 PM

I've been having the same issue too, but are you sure this is the only way of passing objects? Thats a little suprising.

But anyway, good work! :) surely saved me some time

# re: Passing Wpf Objects Between Threads (With Source Code)

Left by Jigar Chhadwa at 12/1/2008 4:44 PM

Hi,

I want to print the Xps file in the background thread. But I am getting the same errors as above.

My UI will call the dll and function as PrintXPS().

I tried many solutions. But nothing works.

Could you please help me to resolve the issue?

# re: Passing Wpf Objects Between Threads (With Source Code)

Left by Nir Dobovizki at 12/2/2008 10:47 AM

Jigar, I'll write an entire post about background printing soon.

# re: Passing Wpf Objects Between Threads (With Source Code)

Left by Jigar Chhadwa at 12/4/2008 8:26 AM

Thanks Nir,

I am really stuck up on that issue. Your help will be very valuable for me.

I will wait for your post on print.

# re: Passing Wpf Objects Between Threads (With Source Code)

Left by kaenneth at 1/20/2009 4:00 AM

What about using the Freezable version of the object?, that should allow other threads to read (but not write) to the object, at which point the new thread makes it's copy.

# re: Passing Wpf Objects Between Threads (With Source Code)

Left by Nir Dobovizki at 1/20/2009 4:54 PM

kaenneth, You are absolutely right – that what I did in the project that inspired this post at the end (after writing this post), the problem is that not everything is Freezable – for example documents in WPF are not Freezable.

# re: Passing Wpf Objects Between Threads (With Source Code)

Left by Stephane at 3/16/2009 11:05 AM

A Huge Thank You, that saved my day!!!

# re: Passing Wpf Objects Between Threads (With Source Code)

Left by May at 5/3/2009 8:14 AM

Hi, for your case this will not work
(I suppose you realy need 2 separate
threads) but I steped into this article windering why my timer bugs me, and the best option for me was to use System.Windows.Threading.DispatcherTimer
it works within UI, without any exceptions about "different thread"

# re: Passing Wpf Objects Between Threads (With Source Code)

Left by Juned Munshi at 10/5/2009 2:12 PM

thanks a million dude...u r guineas

# re: Passing Wpf Objects Between Threads (With Source Code)

Left by kets at 11/1/2010 12:29 PM

Can you please guide me how to read complete pdf files in wpf? Its bit urgent..Please can you help?
Thanx in advance.

# re: Passing Wpf Objects Between Threads (With Source Code)

Left by Nir Dobovizki at 11/1/2010 1:40 PM

Hi kets, sorry can't help you with that, I don't have any experience reading pdf from wpf.

Maybe you should try stackoverflow.com

# re: Passing Wpf Objects Between Threads (With Source Code)

Left by Mike at 7/8/2011 3:47 PM

Not sure if this will help, and it's also in VB.NET, but I add this code to the observable collection class when I have an observable collection that needs to be passed. Maybe there is an equivalent for docs?

Protected Overrides Sub OnCollectionChanged(ByVal e As NotifyCollectionChangedEventArgs)
Dim eventList = CType(CollectionChangedField.GetValue(Me), NotifyCollectionChangedEventHandler)
If eventList IsNot Nothing Then
Using Me.BlockReentrancy
Dim activeDispatcher = (From nh In eventList.GetInvocationList() Let dpo = TryCast(nh.Target, DispatcherObject) Where dpo IsNot Nothing Select dpo.Dispatcher).FirstOrDefault()
If activeDispatcher IsNot Nothing AndAlso Not activeDispatcher.CheckAccess Then
Try
activeDispatcher.BeginInvoke(Sub() MyBase.OnCollectionChanged(e), DispatcherPriority.DataBind)
Catch ex As Exception
End Try
Else
Try
MyBase.OnCollectionChanged(e)
Catch ex As Exception
End Try
End If
End Using
End If
End Sub

# re: Passing Wpf Objects Between Threads (With Source Code)

Left by Mike at 7/8/2011 3:50 PM

And this (forgot to include):

Private Shared ReadOnly CollectionChangedField As FieldInfo = GetType(ObjectModel.ObservableCollection(Of PrintJob)).GetField("CollectionChanged", BindingFlags.NonPublic Or BindingFlags.Instance)

Your comment:



 (will not be displayed)


 
 
Please add 7 and 7 and type the answer here: