Click here to Skip to main content
Click here to Skip to main content

ItemsContainer: Simple ObservableCollection thread safety

, 4 Sep 2013 CPOL
Rate this:
Please Sign up or sign in to vote.
ItemsContainer allows easy sorting and updating of ObservableCollections from async or parallel threads.

Introduction

The ObservableCollection does not work very well in a multi-threaded environment. After it's bound to the UI, it can't be replaced with a new one, even if it's on a notify-property (INotifyPropertyChanged). This requires the user to Clear its contents and Add each new item, triggering a notification message each time. All this has to be done on the UI thread.

Notify-properties, on the other hand, are insulated from the UI. They can be set from any thread and the associated Binding will update the UI without any problems.

ItemsContainer

Therefore to insulate an ObservableCollection from the UI, it can be put inside a container object which is set on a notify-property. Wpf will make updates to the new ObservableCollection inside a container object regardless of the thread. 

ItemsContainer class:

public class ItemsContainer<T> : IEnumerable<T>
{
  public ItemsContainer()
  {
     this.Items = new ObservableCollection<T>();
  }

  public ItemsContainer(IEnumerable<T> items)
  {
     this.Items = new ObservableCollection<T>(items);
  }

  public ObservableCollection<T> Items { get; private set; }

  ...
}

This generic ItemsContainer makes it easy to sort and update items. Just create a new one with the updated items, and set it on a notify-property.

Async ItemsContainer example

Here's an example of a FolderItem class that puts child items in an ItemsContainer. This example sorts an ObservableCollection async.

FolderItem:

public class FolderItem : FileFolderBase, IItemsContainer<FileFolderBase>
{
  ...

  #region Container INotifyPropertyChanged Property

  private ItemsContainer<FileFolderBase> _Container;

  public ItemsContainer<FileFolderBase> Container
  {
     get { return this._Container; }
     set
     {
        if (this._Container != value)
        {
           this._Container = value;
           this.NotifyPropertyChanged("Container");
        }
     }
  }

  #endregion

  ...

  public Task SortAsync(SortKind kind)
  {
     var t = Task.Factory.StartNew(() => 
        {
           this.Sort(kind);            
        });

     return t;
  }

  public void Sort(SortKind kind)
  {
     List<FileFolderBase> items = new List<FileFolderBase>();

     var folders = from f in this.Container.Items
              where f is FolderItem
              orderby f.Name
              select f;

     var files = from f in this.Container.Items
              where f is FileItem
              orderby f.Name
              select f;

     switch (kind)
     {
        case SortKind.NameAscending:
           items.AddRange(folders);
           items.AddRange(files);
           break;
        case SortKind.NameDescending:
           items.AddRange(folders.Reverse());
           items.AddRange(files.Reverse());
           break;
        default:
           break;
     }

     // this will update ObservableCollection items from any thread
     this.Container = new ItemsContainer<FileFolderBase>(items);
  }

  ...
}

XAML:

<!--FileFolderList-->
 <TabItem Header="ListBox" >
    <ListBox ItemsSource="{Binding FileFolderList.Container.Items}">
       <ListBox.ContextMenu>
          <ContextMenu>
             <MenuItem Header="Sort Ascending" Command="{Binding SortListAscending}" />
             <MenuItem Header="Sort Descending" Command="{Binding SortListDescending}" />
          </ContextMenu>
       </ListBox.ContextMenu>
    </ListBox>
 </TabItem>    

View model command handler:

this.SortListAscending.CanExecuteCommand = 
      () => this.ListSortKind != SortKind.NameAscending;

this.SortListAscending.ExecuteCommand = async () =>
{
  await this.FileFolderList.SortAsync(SortKind.NameAscending);
  this.ListSortKind = SortKind.NameAscending;
}; 

Above, a new ItemsContainer and ObservableCollection are created with the updated items and set on the Container notify-property on a non-UI thread. Wpf binds the ListBox ItemsSource to the new ObservableCollection without any problems.

Parallel ItemsContainer example

The ItemsContainer makes parallel processing much simpler. Here's an example where a tree of FolderItems is sorted using Parallel.ForEach.

WpfItemsContainer screen shot:

 

FolderItem code:

public Task SortDescendantItemsAndSelfAsync(SortKind kind)
{
 Task t = Task.Factory.StartNew(() =>
    {
       var folders = this.GetDescedantFoldersAndSelf();
       Parallel.ForEach(folders, folder =>
          {
             folder.Sort(kind);
          });
    });

 return t;
}

XAML:

<!--FileFolderTree-->
<TabItem Header="TreeView" >
<TreeView ItemsSource="{Binding FileFolderTree.Container.Items}"
          MouseRightButtonDown="treeView_MouseRightButtonDown"
          SelectedItemChanged="treeView_SelectedItemChanged"
          >
   <TreeView.ContextMenu>
      <ContextMenu>
         <MenuItem Header="Open folders" Command="{Binding OpenFolders}" />
         <MenuItem Header="Close folders" Command="{Binding CloseFolders}" />
         <Separator />
         <MenuItem Header="Sort Ascending" Command="{Binding SortTreeAscending}" />
         <MenuItem Header="Sort Descending" Command="{Binding SortTreeDescending}" />
      </ContextMenu>
   </TreeView.ContextMenu>
</TreeView>
</TabItem> 

View model command handler: 

this.SortTreeAscending.CanExecuteCommand = 
                  () => this.TreeSortKind != SortKind.NameAscending;

this.SortTreeAscending.ExecuteCommand = async () =>
{
  await this.FileFolderTree.SortDescendantItemsAndSelfAsync(SortKind.NameAscending);
  this.TreeSortKind = SortKind.NameAscending;
};

Obviously, it would be much more difficult sorting each FolderItems' child items on parallel threads then updating each FolderItem's ObservableCollection from the UI thread. It would also make the code more difficult to follow.

Conclusion 

Although the ItemsContainer is a simple idea, it's an effective way to eliminate ObservableCollection thread safety problems.

Benefits of ItemsContainer:

  • Insulates the OC from the UI.
  • OC sorting and updates can be done from any thread. 
  • Makes asynchronous and parallel operations simpler and easier: less code to keep track of.
  • No need to keep track of the UI Dispatcher for updates.
  • Purer MVVM: The UI Dispatcher is actually part of the View and is out of place in the ViewModel. With ItemsContainer, the UI thread is irrelevant which better separates the ViewModel from the View.
  • No illegal thread call exceptions (InvalidOperationException): When OC is connected to the UI, one must ensure all methods that affect it (directly or indirectly) are called from the UI thread. This can get complicated, require additional documentation and potentially trigger illegal thread call exceptions. Theses issues are eliminated using ItemsContainer.
  • More efficient updates: Updating an OC with a Clear call and Add call for each item has a lot of notification overhead. ItemsContainer creates a new OC passing the items directly to the constructor.
  • One can use Wpf's ObservableCollection class without any modifications. 

Drawbacks: 

  • The Binding path is longer: It must go to the ItemsContainer property and then OC Items property.
  • The ItemsContainer/Items relationship could cause confusion to programmers unfamiliar with the code pattern. 
  • An OC property, by itself, can have a name reflective of the items it holds. Although one can name the ItemsContainer, it's not possible to rename the Items OC property without creating a new class.

Points of Interest 

When working with trees, I find it's a lot easier using tree iterators. I've created a simple extensible class that uses a recursive IEnumerable<T> implement that iterates all tree branches and leaves.

ItemsContainerTreeIterator:

public class ItemsContainerTreeIterator<TItem, TItemFilter> : TreeIteratorBase<TItemFilter>
  where TItem : class
  where TItemFilter : class
{
  public ItemsContainerTreeIterator(IItemsContainer<TItem> ancestor)
     : base(ancestor)
  {
  }

  protected override System.Collections.IEnumerable GetParentEnumerable(object currentParent)
  {
     IItemsContainer<TItem> parent = (IItemsContainer<TItem>)currentParent;

     // get ItemsContainer which implements IEnumerable
     var container = parent.GetItemsContainer();

     return container;
  }

  protected override object GetChildAsParent(object Child)
  {
     IItemsContainer<TItem> container = Child as IItemsContainer<TItem>;

     // return object if child is parent; null if child has no child items
     return container;
  }
}

ItemsContainerTreeIterator in action (from FolderItem):

public FolderItem[] GetDescendantFolders()
{
 ItemsContainerTreeIterator<FileFolderBase, FolderItem> it = 
    new ItemsContainerTreeIterator<FileFolderBase, FolderItem>(this);

 return it.ToArray();
}

History 

  • 2013/9/4: Original article.
  • 2013/9/4: Added Conclusion: benefits and drawbacks. 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Ron Waller

Canada Canada
Programming enthusiast. I work with C#, WPF, MVVM, F#, ASP.NET, javascript, jQuery, Css.
Follow on   Twitter

Comments and Discussions

 
QuestionVote of 5 PinmemberGanesanSenthilvel9-Sep-13 6:28 
GeneralMy vote of 5 PinmemberVolynsky Alex5-Sep-13 1:31 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.150327.1 | Last Updated 4 Sep 2013
Article Copyright 2013 by Ron Waller
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid