FlowDocument from a different thread - "The calling thread cannot access this object because a different thread owns it."

  • Posted on: 7 July 2014
  • By: Michał Turecki

The FlowDocument along with most of the elements it contains inherits from DispatcherObject class. This means when constructed, it becomes linked to the thread in which it was constructed and causes several implications.

I'm sure such link was created for a reason - so that FlowDocument objects presented using WPF are not modified in a background thread as the updating is probably not implemented. I also won't debate on why objects contained in FlowDocument have no common, shared way of visiting them (IAddChild interface is helping with adding but something like IHaveChildren is not implemented).

Back to the topic - FlowDocument created by one thread cannot be used in another nor any of each one's inner objects. Or can't it?

One solution includes serializing a document in one thread and deserializing it in another (see this blog entry). This is a slow process as serialization is costly and serialization of a FlowDocument object with images and other graphical elements might not work at all unless XamlPackage format is used - and this one is even slower as requires ZIP compression/decompression.

Another is to create all FlowDocument objects on UI thread (see this SO article). Seems easy to do but what if there are thousand of objects which need to be added to a FlowDocument and at the same time user interface should stay responsive, even if it only needs to display a progress dialog ?

I found out another way to solve this problem, it is kind of hackish but seems to do the job for my needs:

public static void AttachToCurrentThread(this FlowDocument document)
{
	var dispatcher = Dispatcher.CurrentDispatcher;
	var field = typeof(DispatcherObject).GetField("_dispatcher", BindingFlags.NonPublic | BindingFlags.Instance);
	if(field == null)
	{
		throw new InvalidOperationException("_dispatcher property missing on DispatcherObject");
	}
	SetDispatcher(document, field, dispatcher, FlowDocumentVisitors);
}

private static readonly Func<object, object>[] FlowDocumentVisitors =
{
	x => (x is FlowDocument) ? ((FlowDocument) x).Blocks : null,
	x => (x is Section) ? ((Section) x).Blocks : null,
	x => (x is BlockUIContainer) ? ((BlockUIContainer) x).Child : null,
	x => (x is InlineUIContainer) ? ((InlineUIContainer) x).Child : null,
	x => (x is Span) ? ((Span) x).Inlines : null,
	x => (x is AnchoredBlock) ? ((AnchoredBlock) x).Blocks : null,
	x => (x is Paragraph) ? ((Paragraph) x).Inlines : null,
	x => (x is Table) ? ((Table) x).RowGroups : null,
	x => (x is Table) ? ((Table) x).Columns : null,
	x => (x is Table) ? ((Table) x).RowGroups.SelectMany(rg => rg.Rows) : null,
	x => (x is Table) ? ((Table) x).RowGroups.SelectMany(rg => rg.Rows).SelectMany(r => r.Cells) : null,
	x => (x is TableCell) ? ((TableCell) x).Blocks : null,
	x => (x is TableCell) ? ((TableCell) x).BorderBrush : null,
	x => (x is List) ? ((List) x).ListItems : null,
	x => (x is ListItem) ? ((ListItem) x).Blocks : null
};
	
private static void SetDispatcher(object item, FieldInfo field, object value, params Func<object, object>[] selectors)
{
	if(item is DispatcherObject)
	{
		var currentDispatcher = field.GetValue(item);
		if(currentDispatcher != null && currentDispatcher != value)
		{
			field.SetValue(item, value);
		}
	}
	if(item is IEnumerable)
	{
		foreach(var subItem in item as IEnumerable)
		{
			SetProperty(subItem, field, value, selectors);
		}
	}
	if(selectors != null)
	{
		foreach(var selector in selectors.Select(x => x(item)).Where(x => x != null))
		{
			SetProperty(selector, field, value, selectors);
		}
	}
}

Basically it just traverses FlowDocument and it's objects searching for DependencyObject instances and using reflection updates the _dispatcher private variable. This variable name can change in the future, the FlowDocument structure also can change so this solution can be considered not elegant or not stable but having said that DispatcherObject didn't change since .Net 3.5 and AttachToCurrentThread function can be easily adapted to handle different types of FlowDocument blocks and it's inner objects when those inherit from DispatcherObject.

If you are just reading this paragraph then feel free to leave some feedback, especially if I missed any important collections of DispatcherObject instances in the selectors array.

Update:

The previous code was expecting an IEnumerable as input and while this is fine and optimal (less calls) but not flexible enough to handle special cases well.
Unfortunately loading of a packed FlowDocument object, saved using XamlPackage format will fail on a non-STA thread ("The calling thread must be STA, because many UI components require this" exception thrown when TextRange.Load is called on a non-UI thread). This is a show-stopper for me so I can't use the code above anymore but it might become handy for someone not using Images.
I also updated it to support tables and lists and commented out style handling which I did not need (Styles are usually not tied to a thread anyway).
Actually the code can be generalized to handle any object, just change AttachToCurrentThread document argument type to object.

Update 2:
Some cosmetic changes and added a couple additional FlowDocument elements in a visitor.

Comments

I am ltonser. I need help. What can i do it?

I am ltonser. I need help. What can i do it?