July 8th, 2022

Windows Runtime observable collections don’t mix well with multithreading

The Windows Runtime provides observable collections IObservableVector<T> and IObservableMap<K, V>. Observability adds Vector­Changed and Map­Changed events (respectively) to allow you to be called back when the underlying collection changes.

These notifications interact poorly with multi-threading: What happens if while the thread is processing the previous change, another thread tries to mutate the collection?

Different implementations of the observable collection interfaces behave differently.

C#’s observable collections came first. From reading the reference source, we see that mutation methods throw an Invalid­Operation­Exception if they are mutated while a Changed event handler is active. (More detailed discussion in this StackOverflow question.) It is apparent that this object was designed for single-threaded use: The reentrancy checks apply to the object as a whole, regardless of thread. Furthermore, the code doesn’t block reentrancy until it’s about to raise the Changed event. Here’s an abbreviated version:

protected override void InsertItem(int index, T item)
{
    CheckReentrancy();
    base.InsertItem(index, item);

    using (BlockReentrancy())
    {
        /* raise the CollectionChanged event */
    }
}

That creates this multithreaded race condition:

Thread 1 Thread 2
InsertItem  
CheckReentrancy (succeeds)  
base.InsertItem  
  InsertItem
  CheckReentrancy (succeeds)
  base.InsertItem
BlockReentrancy
Raise the event
Handler sees inconsistent collection

Note that Thread 1’s Changed event handler is called after the collection has been changed by Thread 2, so when it goes to look at the collection to find the item that got inserted, it may not actually be there because Thread 2 already changed the collection.

This makes sense because the C# Observable­Collection is explicitly not thread-safe: IsSynchronized always returns false.

The Windows Runtime ValueSet and PropertySet are also observable, and they follow roughly the same model as the C# observable collections they were patterned after: Modifications to the collection are disallowed when a change notification is active. The operation will fail with the exception RO_E_CHANGE_NOTIFICATION_IN_PROGRESS. The Windows Runtime collections do take a little extra care to avoid the “inconsistent collection” problem: The concurrent call from Thread 2 fails rather than passing the initial concurrency check. (Basically by moving the Block­Reentrancy to the top of the function.)

Observable maps created by C++/WinRT follow yet another pattern: They do not block subsequent operations while the Changed event is being raised. This means that handlers in this case have to be prepared for the case that the collection’s state can change out from under them.

Oh, and what about C++/CX? Easy: They simply don’t support concurrency at all!

The C++/CX collection types support the same thread safety guarantees that STL containers support.

The concurrency policy for STL containers is that concurrent reads are permitted, but no other operation can be concurrent with a write.

What does this all mean for you?

Limit your use of observable collections to single-threaded scenarios. Observable collections were originally created for UI data binding, which is single-threaded, and that’s why the observable collection pattern doesn’t extend well to multi-threaded scenarios. Furthermore, do not mutate the collection during the change notification.

If you cannot avoid using observable collections in multi-threaded scenarios, then you have to understand that it’s not going to be a great experience. We’ve found four patterns so far:

Implementation Concurrent write Write during notification
C# Unprotected Rejected
C++/CX Undefined Undefined
ValueSet / PropertySet Allowed Rejected
C++/WinRT single_threaded_… Undefined Undefined
C++/WinRT multi_threaded_… Allowed Allowed

In the bottom row, we see that the C++/WinRT multi-threaded collections allow a write during a change notification. This means that change notification handlers in the bottom row need to be prepared for the possibility that the collection changes while the change handler is running.

We’ll look some more at that bottom row next time.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

2 comments

Discussion is closed. Login to edit/delete existing comments.

  • Neil Rashbrook

    Doesn’t the event give the event handler the item that was inserted and its insert position at the time? In which case, why would Thread 1 go to look at the collection to find the item that got inserted?

  • Joe Beans · Edited

    I put Dispatcher.VerifyAccess() in all my WPF viewmodel SetProperty(...) calls and it ended the BS pretty quick. I created my own ObservableList to replace ObservableCollection because the latter has no method to clear event subscribers to prevent leaks on disposal, and it evolved to become something pretty dreamy. I'm thinking about adding the same Dispatcher.VerifyAccess() calls to public ObservableList methods but I don't know if it will add noticeable lag when adding a quarter million...

    Read more