May 21, 2006

How to control whether my Binding is synchronous or asynchronous

24AsynchronousBinding

I will explain in this post how you can control whether your binding is synchronous or asynchronous. As you know, a synchronous binding will freeze the UI while it’s fetching the data, while an asynchronous binding will start a second thread and keep the UI responsive. Understanding the default behavior will allow you to make the best decision for your particular scenario, whether that is keeping or changing it.

I will distinguish two very different scenarios in this sample:

  • The whole data source takes a while to be available. For example, you are binding to a web service, an RSS feed, or an external XML file. In this case, the actual creation of the data object is not immediate.
  • A property in your data source is slow. In this scenario, the data source is created very quickly, but one of its properties takes a while to return from the getter (for example, the property uses an HttpWebRequest). Note that the .NET guidelines recommend against having slow code in a property getter, and so we actually went back and forth about whether we should support this scenario.

Slow data source

I talked in an earlier post about ObjectDataPovider, and mentioned that you will need it if you want to retrieve your data asynchronously. Well, here is the scenario where you would want to use it: when your whole data source is slow. I simulated a slow data source with the following code:

    public class SlowDataSource
    {
        private string property;

        public string Property
        {
            get { return property; }
            set { property = value; }
        }

        public SlowDataSource()
        {
            Thread.Sleep(3000);
            this.property = "Slow data source";
        }
    }

ObjectDataProvider has an IsAsynchronous property, which when set to true, tells Avalon to create the data source object in a worker thread. Keep in mind that by default the IsAsynchronous property is false, so you have to remember to set it to true. The following code shows how to create the ObjectDataProvider and Binding such that binding to the SlowDataSource object will not block the UI thread:

    ObjectDataProvider odp = new ObjectDataProvider();
    odp.IsAsynchronous = true;
    odp.ObjectType = typeof(SlowDataSource);
    Binding b = new Binding();
    b.Source = odp;
    b.Path = new PropertyPath("Property");
    tbSlowDSSync.SetBinding(TextBlock.TextProperty, b);

We made ObjectDataProvider synchronous by default because we typically expect object sources to be quick to retrieve. XmlDataProvider works similarly, except the IsAsynchronous property is set to true by default. We made it asynchronous by default because we expect it to be used mostly when binding to external XML files and RSS feeds, which can take a while to get to. If you only have 5 lines of XML embedded in your XAML page, however, you may want to set the IsAsynchronous property to false.

Slow property in fast data source

In this second scenario, we get a handle to the data source pretty quickly, but one of its properties can be quite slow to retrieve. Here is a simulated sample of such a data source:

    public class DataSource
    {
        private string slowProperty;

        public string SlowProperty
        {
            get
            {
                Thread.Sleep(3000);
                return slowProperty;
            }
            set { slowProperty = value; }
        }

        public DataSource()
        {
            this.slowProperty = "Slow property";
        }
    }

In this scenario, we don’t have to use ObjectDataProvider anymore. We can create the object in the same thread as the application, and we may want to bind to other fast properties in this same data source synchronously. We want to bind to just that one slow property asynchronously, so we don’t block the UI. To achieve this, we can use the IsAsync property of Binding, as you can see in the following code snippet:

    DataSource source = new DataSource();
    Binding b = new Binding();
    b.Source = source;
    b.IsAsync = true;
    b.Path = new PropertyPath("SlowProperty");
    tbSlowPropAsync.SetBinding(TextBlock.TextProperty, b);

The IsAsync property is false by default, so you have to remember to set it to true in this scenario. We decided to make it false by default because slow setters are bad practice, so we don’t expect this to be a common scenario.

Note that you could just as easily create asynchronous data providers or bindings from within XAML; I made them in code because a few people have asked for examples.

Below is a screenshot of the application. The rectangle on top is animating so you can clearly see the UI freeze when doing the synchronous bindings.

Here you can find the VS project with this sample code. This works with February CTP WPF bits.

10 Comments
  1. Pavan Podila

    Hi Beatriz,
    I am trying to use the scenario where I have a “Slow Property with a Fast DataSource”. I want to know if I can monitor the background thread that is used to asynchronously load the data. Sometimes the asynchronous loading takes so long that I would like to just cancel it. Can you tell me how I can accomplish that?

    Thanks,
    Pavan

    • Bea

      Hi Pavan,

      First of all, here is a quote from the .NET design guidelines:

      ***
      Do use a method, rather than a property, in the following situations:
      The operation is orders of magnitude slower than a field access would be. If you are even considering providing an asynchronous version of an operation to avoid blocking the thread, it is very likely that the operation is too expensive to be a property. In particular operations that access the network or the file system (other than once for initialization) should likely be methods, not properties.
      ***

      One technique I often use is to have a slow method to change the data and a fast cached property with the value. Something like this:

      class MyData : INotifyPropertyChanged
      {
      private object _cachedValue;
      public object MyProperty { get { return _cachedValue; } } // this is fast

      public void ChangeProperty() // this is slow
      {
      do work to compute the new value (possibly on a separate thread);
      _cachedValue = newValue;
      OnPropertyChanged(“MyProperty”);
      }
      }

      Knowing that, if you still really need to cancel your binding, here is how you can do it:
      BindingOperations.ClearBinding(…).

      Let me know if this helped.

      Bea

  2. Dan

    Hi Beatriz,

    I am trying to set up a ‘slow datasource’ type binding, as I have a TreeView bound to a method that builds data from a SQL Server. However, when I set IsAsynchronous to True, I get an exception at runtime saying ‘Must create DependencySource on same Thread as the DependencyObject’. My ObjectDataProvider is set up as:

    <ObjectDataProvider MethodName=”CreateData” x:Key=”ObjectData” ObjectType=”{x:Type me:DataAccess}” IsAsynchronous=”True”/>

    and the DataAccess.CreateData method creates an ObservableCollection<>, into which it places the data. Can you suggest any reason why this is failing ?

    Many thanks,

    Dan

    • Bea

      Hi Dan,

      I am not able to repro the behavior you describe. I created a sample with the code you describe (which you can find here), but I am not able to cause the exception. Do you have a way to share your sample app?

      Thanks,
      Bea

      • Dan

        Hi Bea,

        Many thanks for the reply; I gradually altered your working code to look more like mine until it broke The cause is the following:

        My CreateData method creates an ObservableCollection, where MyDataClass exposes a dependency property (’Text’). My databound control then uses a DataTemplate like the following:

        (DataTemplate x:Key=”SingleItemTemplate”)
        (TextBlock Text=”{Binding Text}”/)
        (/DataTemplate)

        It seems that the presence of the dependency property on MyDataClass is what’s causing the exception; I’ve changed the property to a normal instance property and it all seems to be working now (which leads me to wonder why I used a dependency property in the first place !).

        If you have any further insight, then I’d be very interested, but in any case it’s working now.

        Thanks again,

        Dan

        P.S. Apologies if this double-posts

        • Bea

          Dan,

          I’m glad you were able to figure out your issue.

          In general, I advise people to not use DependencyProperties in the data source, so that your source can be easily reused by technologies other than WPF. However, using DependencyProperties in the source should work the same way as using CLR properties, and we have support for property change notifications in a different thread.

          Do you have a way to share your repro?

          Thanks,
          Bea

        • Dan

          Hi Bea,

          Thanks for the reply; after some searching, I’ve worked out why I used a dependency property in the first place - I want to be able to make changes to the datasource and have it immediately reflected in the databound control (i.e. select an item on the tree/list, edit it using the form, and display the changes).

          I’ve uploaded a sample project that demonstrates the scenario ; change the datasource to asynchronous to show the exception.

          Have fun :)

          Dan

        • Bea

          Hi Dan,

          I am able to see the exception in your repro. As you mention, the combination of having an asynchronous DataProvider and having a DependencyProperty are enough to cause the exception to happen. The dev for this feature will be investigating this.

          As for the property changes - you don’t need to use a DependencyProperty to have property change notifications. You can have normal CLR properties and use an interface called INotifyPropertyChanged to provide the notifications you need. If that is the only reason why you’re using DependencyProperties, I would still discourage that.

          I hope this helps,
          Bea

        • Bea

          Dan,

          It turns out that this exception is the result of a by design limitation of the property engine. The property engine’s dependency graph only works when all the players come from the same thread. In your case, the source and target of the bindings live in different threads, which causes the exception.

          Thanks for letting us know anyway, and keep the feedback coming!

          Bea

        • Dan

          Ah INotifyPropertyChanged, I should’ve thought of that !

          Thanks for your help, it’s all working nicely now

          Cheers,

          Dan

Comments are closed.