4

We're working with OData on Silverlight, using DataServiceCollection to get the data.

All calls for fetching data (LoadAsync() LoadNextPartialSetAsync()) are done on a worker thread. However, the 'LoadCompleted' callback as well as deserialization and object materialization are done the UI thread.

We decompiled the System.Data.Services.Client.DLL where the DataServiceCollection is, and saw that indeed all code handling the response of the OData is dispatched to the UI thread.

Is there any way to get the deserialization to be called on a worker thread instead?

thanks Yaron

Yaron
  • 2,053
  • 2
  • 19
  • 21

3 Answers3

3

Well...

It seems the OData collections deliberately moves processing the UI thread. I'm guessing this is done because old objects might have properties the UI is bound to. These properties might change when loading additional data.

Using the query itself, I was able to get the response on a worker thread. However, doing this means one MUST detach the objects from the OData context (or clone them) if UI is bound to any property. Otherwise consequent queries might cause property changed events when objects are materializing on the worker thread.

Yaron
  • 2,053
  • 2
  • 19
  • 21
  • I’m having a hard time understanding how you were able to materialize on the worker thread. In my case, calling LoadAsync() from any other thread than the UI one means losing the browser’s authorization and getting back 401s (I’m using the default XmlHttp stack so that the embedded SL app uses the existing web app authentication for its service calls). – Eduard Uta Jul 07 '17 at 15:02
  • 1
    Honestly, this is from almost 6 years ago and I have no memory of what I did there exactly :) – Yaron Jul 08 '17 at 18:12
1

The problem you have is that the DataServiceCollection<T> is derived from the ObservableCollection<T>. Which in turn is designed to be bound to UI elements. When changes are made to the membership of a ObservableCollection<T> a binding expression observing it is notified. This binding expression then attempt to update the target UI element. If the notification arrives on a non-UI Thread then an exception occurs.

Hence the DataServiceCollection<T> deliberately shifts materialisation to the UI Thread so that as items appear in the collection the resulting change notifications do not result in an exception. If this behaviour is unacceptable to you then DataServiceCollection<T> is not for you.

Instead execute the query yourself via the DataServiceQuery<T>.BeginExecute. The callback you pass to BeginExecute will execute on a worker thread (at least it will when ClientHTTP is being used, I have yet established what will happen when XmlHttp is being used). Here you can enumerate the results and place them in whatever collection type your prefer. You can the switch to the UI thread when you are ready to display the results.

AnthonyWJones
  • 187,081
  • 35
  • 232
  • 306
  • Update: ODAta keeps the objects it loads in the context. In our App we load part of the data, move it to the UI (to get the UI up quickly), and then load the rest and update the UI. When materializing the rest of the objects, they get connected to the objects already loaded, which are found in the OData context, and also bound in the UI. This means that the materialization can't be done on the UI thread. The only way to load the data in parts on a background thread, is to create clones which are detached from the context. I guess it will have to do for now. – Yaron Aug 31 '11 at 07:41
0

The callback will be called always on a UI thread. If the request was using the XmlHttp stack (which is the default if you call it from a UI thread) then the networking stack invokes the callback registered by the WCF Data Service on the UI thread. So in this case it's a behavior of the DataServiceCollection/DataServiceContext but a behavior of the underlying network stack. If you call the request from a non-UI thread or you explicitely set the Http stack to be Client then the callback will come back on a non-UI thread (potentially a different one). We still move it back to the UI thread before letting the caller know. The reason for this is consistency, especially since you can't interact with UI elements on background threads.

If you would execute the query manually, so for example through DataServiceContext.BeginExecute, then the materialization (or most of it anyway) is driven by the caller, since the call returns just IEnumerable which is not yet populated. If you would then transfer execution to a worker thread and enumerate the results there, the materialization will happen on that thread.

Just curious, why do you want to move it? Are you processing so much data, that it causes a visible UI lag?

Vitek Karas MSFT
  • 13,130
  • 1
  • 34
  • 30
  • Whilst I hesitate to question someone who places the letters "MSFT" after their name, however: 1) The DataServiceCollection most definitely does shove things deliberately to the UI Thread. 2) I believe that `DataServiceContext.BeginExecute` will always execute the callback on a work thread when the HttpStack chosen in ClientHttp (what it does under the insane XmlHttp is anyone's guess). Of course I could wrong but I don't think so. – AnthonyWJones Aug 29 '11 at 18:00
  • Vitek, your comment is not true at all. WCF will process the network reply on threads based on the request thread. If your request is done on the UI thread it will process the reply on the UI thread, otherwise it won't. This behavior can be changed somehow by an attribute if I remember correctly. As for DataServiceCollection it has EXPLICIT code to delegate the process to the UI thread. Using reflector's integration with VC I could actually put a breakpoint there and see the HTTP reply coming back on a worker thread and delegated to the UI thread. – Yaron Aug 30 '11 at 07:50
  • I'm sorry, the response I gave was a bit confusing and partially wrong. It's true if you're using XmlHttp which is the default and calling the request from a UI thread. It's not true for the Client stack which is used if you invoke the request from a non-UI thread. I updated the response above to reflect this. – Vitek Karas MSFT Aug 30 '11 at 17:02