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?
Update
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
Update #2 - A solution
(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;
}