{"id":106847,"date":"2022-07-08T07:00:00","date_gmt":"2022-07-08T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=106847"},"modified":"2022-11-30T06:53:46","modified_gmt":"2022-11-30T14:53:46","slug":"20220708-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220708-00\/?p=106847","title":{"rendered":"Windows Runtime observable collections don&#8217;t mix well with multithreading"},"content":{"rendered":"<p>The Windows Runtime provides observable collections <code>IObservableVector&lt;T&gt;<\/code> and <code>IObservableMap&lt;K, V&gt;<\/code>. Observability adds <code>Vector\u00adChanged<\/code> and <code>Map\u00adChanged<\/code> events (respectively) to allow you to be called back when the underlying collection changes.<\/p>\n<p>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?<\/p>\n<p>Different implementations of the observable collection interfaces behave differently.<\/p>\n<p>C#&#8217;s observable collections came first. From <a href=\"https:\/\/referencesource.microsoft.com\/#System\/compmod\/system\/collections\/objectmodel\/observablecollection.cs\"> reading the reference source<\/a>, we see that mutation methods throw an <code>Invalid\u00adOperation\u00adException<\/code> if they are mutated while a Changed event handler is active. (More detailed discussion in <a href=\"https:\/\/stackoverflow.com\/questions\/6247427\/blockreentrancy-in-observablecollectiont\"> this StackOverflow question<\/a>.) 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&#8217;t block reentrancy until it&#8217;s about to raise the Changed event. Here&#8217;s an abbreviated version:<\/p>\n<pre>protected override void InsertItem(int index, T item)\r\n{\r\n    CheckReentrancy();\r\n    base.InsertItem(index, item);\r\n\r\n    using (BlockReentrancy())\r\n    {\r\n        \/* raise the CollectionChanged event *\/\r\n    }\r\n}\r\n<\/pre>\n<p>That creates this multithreaded race condition:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<td style=\"border: 1px gray; border-style: none solid solid none;\">Thread 1<\/td>\n<td style=\"border-bottom: solid 1px gray;\">Thread 2<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">InsertItem<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">CheckReentrancy (succeeds)<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">base.InsertItem<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">\u00a0<\/td>\n<td>InsertItem<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">\u00a0<\/td>\n<td>CheckReentrancy (succeeds)<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">\u00a0<\/td>\n<td>base.InsertItem<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">BlockReentrancy<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">Raise the event<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">Handler sees inconsistent collection<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Note that Thread 1&#8217;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.<\/p>\n<p>This makes sense because the C# Observable\u00adCollection is explicitly not thread-safe: <code>IsSynchronized<\/code> always returns <code>false<\/code>.<\/p>\n<p>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 <code>RO_E_<wbr \/>CHANGE_<wbr \/>NOTIFICATION_<wbr \/>IN_<wbr \/>PROGRESS<\/code>. The Windows Runtime collections do take a little extra care to avoid the &#8220;inconsistent collection&#8221; problem: The concurrent call from Thread 2 fails rather than passing the initial concurrency check. (Basically by moving the <code>Block\u00adReentrancy<\/code> to the top of the function.)<\/p>\n<p>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&#8217;s state can change out from under them.<\/p>\n<p>Oh, and what about C++\/CX? Easy: They simply don&#8217;t support concurrency at all!<\/p>\n<blockquote class=\"q\"><p>The C++\/CX collection types support <a href=\"https:\/\/docs.microsoft.com\/en-us\/cpp\/cppcx\/collections-c-cx?view=msvc-160\"> the same thread safety guarantees that STL containers support<\/a>.<\/p><\/blockquote>\n<p>The concurrency policy for STL containers is that concurrent reads are permitted, but no other operation can be concurrent with a write.<\/p>\n<p>What does this all mean for you?<\/p>\n<p>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&#8217;s why the observable collection pattern doesn&#8217;t extend well to multi-threaded scenarios. Furthermore, do not mutate the collection during the change notification.<\/p>\n<p>If you cannot avoid using observable collections in multi-threaded scenarios, then you have to understand that it&#8217;s not going to be a great experience. We&#8217;ve found four patterns so far:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>Implementation<\/th>\n<th>Concurrent write<\/th>\n<th>Write during notification<\/th>\n<\/tr>\n<tr>\n<td>C#<\/td>\n<td>Unprotected<\/td>\n<td>Rejected<\/td>\n<\/tr>\n<tr>\n<td>C++\/CX<\/td>\n<td>Undefined<\/td>\n<td>Undefined<\/td>\n<\/tr>\n<tr>\n<td>ValueSet \/ PropertySet<\/td>\n<td>Allowed<\/td>\n<td>Rejected<\/td>\n<\/tr>\n<tr>\n<td>C++\/WinRT single_threaded_&#8230;<\/td>\n<td>Undefined<\/td>\n<td>Undefined<\/td>\n<\/tr>\n<tr>\n<td>C++\/WinRT multi_threaded_&#8230;<\/td>\n<td>Allowed<\/td>\n<td>Allowed<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>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.<\/p>\n<p>We&#8217;ll look some more at that bottom row next time.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Concurrent observation and mutation is a bit of a mess.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-106847","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Concurrent observation and mutation is a bit of a mess.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106847","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=106847"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106847\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=106847"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=106847"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=106847"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}