1

Have an object with most of properties very lightweight - text up to 200 character. One property is FlowDocument that can be large and want to retrieve it Async. It fails when I set Async = True with the message: "The calling thread cannot access this object because a different thread owns it."

    <FlowDocumentReader Name="FlowDocumentPageViewer1" HorizontalAlignment="Stretch" 
        Document="{Binding Source={x:Static Application.Current}, Path=MyGabeLib.Search.SelectedDoc.DocFlowDocument, Mode=OneWay, IsAsync=True}" />

Production Get is more complex but the same failure on IsAsyc True even with a simple hard coded FlowDocument.

    public FlowDocument DocFlowDocument
    {
        get
        {
            FlowDocument docFlowDocumentFast = new FlowDocument();
            Paragraph p = new Paragraph();
            Run r = new Run();
            r.Foreground = System.Windows.Media.Brushes.Red;
            r.Text = "Hard Code Simple FlowDocument";
            p.Inlines.Add(r);
            docFlowDocumentFast.Blocks.Add(p);
            return docFlowDocumentFast;
        }
    {

It does call SelectedDoc.DocFlowDocument and the document is returned! With IsAsync = False it runs just fine. I think the problem is the Static Source but clearly I don't know how to fix it.

    public partial class App : Application
    {
        private static GabeLib staticGabeLib = new GabeLib();

        private GabeLib myGabeLib = staticGabeLib;

        public GabeLib MyGabeLib
        { get { return myGabeLib; } }


        public static GabeLib StaticGabeLib
        { get { return staticGabeLib; } }
    }

When GabeLib starts it reads in application and user setting from a database.

If there is a better way to approach this I will give it a try. Since the FlowDocument can be 3 mb and all the other properties 10K combined this is big performance hit and the most used button is next object. The FlowDocument comes from from a varchar(max) in SQL and gets formatted with line breaks and words highlighted. It is not just big - compared to the other properties it is also expensive.

The FlowDocumentReader itself appears to have some async support as on a large document I get the first page fast and then pages load at about 100/second. But I would still like it to get page 1 after all the other properties are retrieved.

The problem was as martin stated "Since a FlowDocument is a dispatcher object it can only be accessed from the thread that created it".

The solution was to serialize to a XAML string.

    public string XAMLdocFlowDocument
    {
        get 
        {
            Thread.Sleep(6000);
            return XamlWriter.Save(FlowDocumentSlow); 
        } 
    }

Bind to the string with a converter

   <FlowDocumentReader Grid.Row="3" Grid.Column="0" VerticalAlignment="Stretch" 
        Document="{Binding Path=XAMLdocFlowDocument, IsAsync=True,
        Converter={StaticResource flowDocumentToXamlConverter}, Mode=OneWay}" />

Converter

    [ValueConversion(typeof(string), typeof(FlowDocument))]
    public class FlowDocumentToXamlConverter : IValueConverter
    {
        #region IValueConverter Members

        /// <summary>
        /// Converts from XAML markup to a WPF FlowDocument.
        /// </summary>
        public object Convert(object value, System.Type targetType,
        object parameter, System.Globalization.CultureInfo culture)
        {
            var flowDocument = new FlowDocument();
            if (value != null)
            {
                var xamlText = (string)value;
                flowDocument = (FlowDocument)XamlReader.Parse(xamlText);
            }

            // Set return value
            return flowDocument;
        }
paparazzo
  • 44,497
  • 23
  • 105
  • 176
  • Whats the FallbackValue in this case? Does that need to be specified in the binding (as this might be applied while async op is processing?) – Ricibob Sep 16 '11 at 15:37
  • Still fails with FallbackValue. If I do PriorityBinding it will get past the non async and even call the async get but then fails in the XMAL. – paparazzo Sep 16 '11 at 18:41
  • Why the downvote? What is not clear, shows lack of research, or is not useful? – paparazzo Sep 17 '11 at 22:24
  • Thank again to Marten. I recently upgraded the code to use BackgroundWorker rather than IsAsync=True and got a 10X performance improvement. IsAsync=True is great if you have an expensive process that returns a small amount of text otherwise use BackgroundWorker or threading directly. Dispatcher is complex but it is there for a good reason. – paparazzo Nov 30 '11 at 02:22

1 Answers1

2

Without seeing the code I'm guessing that the FlowDocument is created (and loaded) when the property is read. This is done in a background thread since the property binding is async.

Since a FlowDocument is a dispatcher object it can only be accessed from the thread that created it, which in this case is the background thread that read the property.

So, the created FlowDocument cannot be accessed by your UI-thread.

You'll need another approach for loading the document asynchronously.

Perhaps you could use synchronous (normal) binding and use XamlReader.LoadAsync to load the document? I haven't tried it myself but I'm guessing it's worth experimenting with.

Mårten Wikström
  • 11,074
  • 5
  • 47
  • 87
  • I added the the get to the question. What is interesting it that it calls the get and the get returns the flowdocument without an error. When the UI tries to bind it fails. It seem like the call to get should be on the proper UI thread. According to the documentation all you need to do is add the IsAsync = True and it turns a regular get into an asynchronous call. I will look at you recommendation but to be honest I don't fully understand it. – paparazzo Sep 16 '11 at 23:04
  • The get call is done asynchronously (on another thread). The problem is that you're creating a new FlowDocument instance in the get method. Even though that instance is returned correctly it cannot be used by your UI thread since it was created by another thread (the one that made the get call). That is why an error does not occur until the UI attempts to "use" the returned FlowDocument instance. – Mårten Wikström Sep 16 '11 at 23:32
  • You'll need to create the FlowDocument instance on the UI thread. So the question then is how you should load it asynchronously. – Mårten Wikström Sep 16 '11 at 23:40
  • I think I agree but I don't know how. According to the documentation that should all be taken care of for me. And I even see non Microsoft code samples that state they work. I think the problem is in the static source and it is possibly a bug. I still give you a +1. – paparazzo Sep 17 '11 at 00:25
  • @BalamBalam: How are you currently loading content into the FlowDocument? – Mårten Wikström Sep 17 '11 at 09:01
  • In production as stated in the question the FlowDocument comes from a VarChar(Max) and is formatted and highlighted. The highlight is dynamic with the words in the search. But it also fails in the same manner with a simple hard coded FlowDocument that I added to the problem statement. I don't think the problem is with the FlowDocument. The get (assessor) is called and returns a FlowDocument without error. – paparazzo Sep 17 '11 at 22:27
  • The problem is not the get accessor. The problem is that it is called from a background thread. You will have to find a way to do your syntax highlighting in an async manner. How to do that is another topic, so give it a shot and post a new question if you have more problems. – Mårten Wikström Sep 18 '11 at 10:33
  • Marten I appreciate your help. But, this simple sample works (I have loaded it on my PC) http://msdn.microsoft.com/en-us/library/ms753174.aspx I still don't understand what is different about the property in my application. If the UI thread is smart enough to call the assessor it seems like it should be smart enough to bind to the response. – paparazzo Sep 18 '11 at 17:58
  • I don't know if I can be much clearer but I'll try once more: You cannot create a FlowDocument on one thread and then use it from another thread. That is by design. Since you're using an async binding the FlowDocument is not created by the UI thread. That's your problem. Note: There is nothing wrong with the binding. – Mårten Wikström Sep 18 '11 at 20:06
  • In the end it was easy. On the server end return a string property using return XamlWriter.Save(FlowDocumentSlow). Bind to the string and then use a converter to get back the FlowDocument using flowDocument = (FlowDocument)XamlReader.Parse(string(value)). I marked your answer as an answer. I trust if you gave the question a -1 you will take it back - you learned this once yourself. – paparazzo Sep 19 '11 at 01:52
  • Good news that you've resolved your problem. BTW: I did not downvote your question. Learning new stuff is the best part of coding! :-) – Mårten Wikström Sep 19 '11 at 03:31