Dispatcher and UI Thread Access
In WPF, every control belongs to the thread that created it, and the WPF TextControl is a control like any other: the UI thread of its window owns it, and the Dispatcher of that thread is the only way in from anywhere else. Knowing this model tells you which TX Text Control objects your background code may touch, how a document crosses from one thread to another, and how a result gets back into the editor.
How WPF Assigns Controls to Threads
TextControl in the TXTextControl.WPF namespace derives from System.Windows.Controls.Control. Like every WPF element, it is bound to the thread that created it, the UI thread of the window, and WPF throws an InvalidOperationException when another thread calls it. The Dispatcher of that thread runs the window's work one item at a time: input, layout, rendering and your event handlers. As long as one of your handlers runs, nothing else in the window happens.
| Object | May be used on | Reason |
|---|---|---|
The WPF TextControl, ButtonBar, RulerBar, StatusBar and ribbon | The UI thread of their window | WPF controls, bound to the thread that created them |
| ServerTextControl | Any thread, one instance per thread | A System.ComponentModel.Component without a user interface, meant for work in the background |
| MailMerge | The thread of the text component it works on | It merges into the document of its TextComponent |
| Byte arrays with documents | Any thread | Plain data, which your code passes between threads |
Load, Save and the merge methods of MailMerge are synchronous. Called in a handler, they occupy the dispatcher until they return, and the window freezes for that time. Background Operations moves such work to other threads.
Hand a Document to Another Thread
The document of the editor can't leave the UI thread, but a copy of it can. The following handler in MainWindow.xaml.cs copies the editor's document into a byte array, creates a PDF file from it in a ServerTextControl on a thread of the thread pool and returns to the window afterward. It needs a button named exportPdfButton in MainWindow.xaml:
using System;
using System.Threading.Tasks;
using System.Windows;
using TXTextControl;
private async void ExportPdf_Click(object sender, RoutedEventArgs e)
{
// On the UI thread: copy the editor's document
textControl1.Save(out byte[] document, BinaryStreamType.InternalUnicodeFormat);
exportPdfButton.IsEnabled = false;
try
{
await Task.Run(() =>
{
// On a thread of the thread pool: no WPF element in here
using (ServerTextControl tx = new ServerTextControl())
{
if (!tx.Create())
{
throw new InvalidOperationException("TX Text Control could not be initialized.");
}
tx.Load(document, BinaryStreamType.InternalUnicodeFormat);
tx.Save("document.pdf", StreamType.AdobePDF);
}
});
MessageBox.Show(this, "document.pdf created.");
}
finally
{
// Back on the UI thread after await
exportPdfButton.IsEnabled = true;
}
}
The code works as follows:
- WPF.TextControl.Save with BinaryStreamType
InternalUnicodeFormatcopies the complete document, in the TX Text Control format, into a byte array. The user can keep editing while the copy is exported. - The lambda of
Task.Runuses only the byte array and aServerTextControlit creates itself. Create returnsfalseif the instance couldn't be initialized. awaitin an event handler of the window continues on the UI thread, so the message box and the button are used on the thread that owns them. An exception of the task is thrown again there; Catch Errors of Background Work shows how to handle it.
Get Back to the UI Thread With the Dispatcher
Code that runs on another thread and isn't awaited from a handler, such as a callback of another component or a timer of System.Threading, reaches the editor through its Dispatcher. CheckAccess tells whether the current thread owns the control, and InvokeAsync queues a call on the owning thread:
using System;
using System.Windows;
using TXTextControl;
private void ShowDocument(byte[] document)
{
// Called on another thread: queue the call on the UI thread
if (!textControl1.Dispatcher.CheckAccess())
{
textControl1.Dispatcher.InvokeAsync(() => ShowDocument(document));
return;
}
textControl1.Load(document, BinaryStreamType.InternalUnicodeFormat);
}
The code works as follows:
Dispatcher,CheckAccessandInvokeAsyncare members that every WPF control inherits.InvokeAsyncreturns at once, so the background thread doesn't wait for the window.- WPF.TextControl.Load replaces the document of the editor and runs on the UI thread.
For reports that arrive regularly, such as the progress of a long task, Progress<T> does the same with less code: created on the UI thread, it calls its handler there for each report, as Background Operations shows.
Keep the Dispatcher Free
- Don't wait on the UI thread. A handler that calls
Wait()or readsResultof a task blocks the dispatcher just asLoaddoes, and when the task itself needs the UI thread, neither can go on. Useawaitinstead. - Split work that must stay in the editor. Some work needs the
TextControlitself, such as building a document the user watches. Build larger parts in aServerTextControland insert them in one step, as Large Documents shows. - Start after Loaded. The control works with documents once WPF has loaded it into the window, so startup code belongs in its
Loadedevent, as Control Initialization explains. - Catch what nobody catches. An exception on the UI thread that no handler catches ends the application. The TX Words samples handle
Application.Current.DispatcherUnhandledException, as Dispatcher and Thread Access in Troubleshooting describes.
Next Steps
- Background Operations — convert and merge on other threads with progress and cancellation.
- Using ServerTextControl in Desktop Applications — the lifecycle of
ServerTextControl. - Architecture and Control Lifecycle — where code belongs in the life of the control.
- Memory Usage and Resource Disposal — dispose every instance a thread creates.