I'm having a bit of an issue with a RTB and document generation in regards to threads.
When the TextChanged event fires on the RTB, a new thead is created, and the document generation is offloaded to this. This can take a couple of seconds, with blocking calls, so it really needs to be on another thread to keep the UI responsive.
The problem I'm having is an exception when I try to add the newly generated document to the Document
property of the RTB. ( The calling thread cannot access this object because a different thread owns it.) This is not due to forgetting to use Dispatcher.Invoke
, as thats where the exception is generated, but because I'm creating the FlowDocument/Paragraph/Run instances on a thread other than the UI thread(I think??).
Is there a way to achieve what I'm looking for here?
private void rtbQuery_TextChanged(object sender, TextChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("Requires update; on thread:" + System.Threading.Thread.CurrentThread.ManagedThreadId);
backgroundWorker.RunWorkerAsync();
}
private void backgroundWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
System.Diagnostics.Debug.WriteLine("Generating; on thread:" + System.Threading.Thread.CurrentThread.ManagedThreadId);
DocumentGenerator dgen = new DocumentGenerator();
string queryText = getQueryText();
e.Result = dgen.GenerateDocument(queryText);
}
private void backgroundWorker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("Assigning; on thread:" + System.Threading.Thread.CurrentThread.ManagedThreadId);
FlowDocument doc = (FlowDocument)e.Result;
txtQuery.Document = doc; // ! The calling thread cannot access this object because a different thread owns it
}
>Requires update; on thread:9
>Generating; on thread:10
>Assigning; on thread:9
(of sorts)
So, as @Jon Mitchell pointed out, I cant update my RTB on the UI thread, with an object created on another thread. There is a very simple solution, that requires minimal code change, to work around this though, and i'm posting it up to save future people the hassle. Briefly explained, an object graph is created on the other thread, and then converted to XAML. The UI thread then converts this XAML back to the object graph, in its own thread, and everything works a-ok.
private void backgroundWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
DocumentGenerator dgen = new DocumentGenerator();
string queryText = getQueryText();
dgen.GenerateDocument(queryText); // start generation
e.Result = dgen; // note, i'm passing the generator, not the document
}
private void backgroundWorker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
DocumentGenerator dgen = (DocumentGenerator)e.Result;
txtQuery.Document = dgen.GetFlowDocument();
}
In DocumentGenerator class
public void GenerateDocument(string data)
{
... // build up the document DOM
// return documentDOM; // used to return the generated item here.
documentXAML = System.Windows.Markup.XamlWriter.Save(documentDOM); // serialize the DOM to XAML
}
public FlowDocument GetDocument()
{
object result = System.Windows.Markup.XamlReader.Parse(documentXAML); // build DOM from XAML
return (FlowDocument)result;
}
I think the issue is because the FlowDocument
is a DependencyObject
which isn't freezable and therefore can't be created on one thread and then used on a different one. I think its because when the FlowDocument
is created on the other thread it has a different dispatcher, to the RTB.
A workaround for this can be found on AAron's blog:
FlowDocument doc = new FlowDocument(new Paragraph(new Run("hi"))); System.IO.MemoryStream stream = new System.IO.MemoryStream(); XamlWriter.Save(doc, stream); stream.Position = 0; this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ZeroDelegate)delegate () { flowDoc = (FlowDocument)XamlReader.Load(stream); });
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With