Updated ObservableCollection in another thread

Topics: Prism v2 - Silverlight 3
Apr 1, 2010 at 7:38 AM

Here's my situation - I followed tutorials in Channel 9 (http://channel9.msdn.com/posts/akMSFT/Creating-a-modular-application-using-Prism-V2-Part-1-of-4--Creating-a-shell-and-modules/) to create my first Prism silverlight project and everything worked fine.

Then I switched the sample Digg service to a RIA data service call. The service call, of course, was asynchronized and my ObservableCollection was updated on a different thread. And sure enough, I had cross-thread exception. So, I modified my code to make sure that the Observable collection was both created and updated on UI thread by using Application.Current.Dispatcher.BeginInvoke(). The exception was gone, and when I traced the code, the collection was indeed populated correctly. BUT, the problem was that the UI was not updated.

The following is the code for my ViewModel:

public class NetFlexResultsViewModel
     {
        private INetFlexService mNetFlexService;
        public NetFlexResultsViewModel(INetFlexService netFlexService, IEventAggregator aggregator)
        {
            Application.Current.RootVisual.Dispatcher.BeginInvoke(() =>
                {
                    Items = new ObservableCollection<NetFlexResultItem>();
                    mNetFlexService = netFlexService;
                    aggregator.GetEvent<SearchEvent>().Subscribe(OnSearch, Microsoft.Practices.Composite.Presentation.Events.ThreadOption.UIThread);
                });
        }
        //note this method has to be public as in CAB 
        public void OnSearch(string title)
        {
            mNetFlexService.BeginSearch(title,
               new Action<System.Collections.Generic.IEnumerable<NetFlexResultItem>>(
                   (res) =>
                   {
                       foreach (NetFlexResultItem item in res)
                           Items.Add(item);

                   }));
        }
        public ObservableCollection<NetFlexResultItem> Items { get; private set; }

        public string HeaderInfo
        {
            get { return "NetFlex results"; }
        }
    }
And my view is simple (see below).I can't figure out what is wrong. Please help!:
<UserControl x:Class="Odyssey.NetFlexResults"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
    
    <Grid x:Name="LayoutRoot" Background="White">
        <ListBox Name="ItemList" ItemsSource="{Binding Items}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</UserControl>
Oct 15, 2010 at 8:51 PM

Hi Haishi,

You are correct in that you have to update the code to support asynchronous service calls. Nevertheless, you must take into account that in most general scenarios VM code is executed on the UI thread, but the service call is executed on a different thread (not to block the UI).

That said, you can try removing the use of the Application.Current.Dispatcher.BeginInvoke() in the VM constructor. It is not necessary as you are probably already running on the UI thread, and as the Items property is instantiated asynchronously on a later time bindings will probably fail.  

public NetFlexResultsViewModel(INetFlexService netFlexService, IEventAggregator aggregator)
{
       Items = new ObservableCollection<NetFlexResultItem>();
       mNetFlexService = netFlexService;
       aggregator.GetEvent<SearchEvent>().Subscribe(OnSearch, Microsoft.Practices.Composite.Presentation.Events.ThreadOption.UIThread);
}

In general callbacks from the service are executed on the calling thread (UIThread) in this case, so you won’t need to update the OnSearch method. If  need to guarantee that the Items property is accessed from the UI thread:

public void OnSearch(string title)       
{           
               mNetFlexService.BeginSearch(title,
               new Action<System.Collections.Generic.IEnumerable<NetFlexResultItem>>(
                   (res) =>
                   {
                       foreach (NetFlexResultItem item in res)
                       {
Application.Current.RootVisual.Dispatcher.BeginInvoke(()=>
Items.Add(item) );
                       }
                   }));
}

You can also take a look at the MVVM Quickstart (GetNewQuestionnaireInstance method of the QuestionnaireVM), that uses an asynchronous call with a call back that updates the UI.
On the other hand, I don’t see in your code where you are setting the VM as the datacontext for the V, but I assume that is correctly done somewhere else.

Fernando Antivero
http://blogs.southworks.net/fantivero