Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit acb4fbd

Browse files
justinvpdanmoseley
authored andcommitted
ObservableCollection: Avoid SimpleMonitor allocation (#20643)
* ObservableCollection: Avoid SimpleMonitor allocation Brings back the optimization added in 9e7cc5c (and subsequently reverted in 6393248), but in terms of the existing private SimpleMonitor class to maintain (de)serialization compatibility with desktop. * Add TypeForwardedFrom
1 parent d80bdab commit acb4fbd

File tree

1 file changed

+46
-13
lines changed

1 file changed

+46
-13
lines changed

src/System.ObjectModel/src/System/Collections/ObjectModel/ObservableCollection.cs

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Collections.Specialized;
77
using System.ComponentModel;
88
using System.Diagnostics;
9+
using System.Runtime.Serialization;
910

1011
namespace System.Collections.ObjectModel
1112
{
@@ -17,6 +18,7 @@ namespace System.Collections.ObjectModel
1718
[Serializable]
1819
[DebuggerTypeProxy(typeof(CollectionDebugView<>))]
1920
[DebuggerDisplay("Count = {Count}")]
21+
[System.Runtime.CompilerServices.TypeForwardedFrom("WindowsBase, Version=3.0.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")]
2022
public class ObservableCollection<T> : Collection<T>, INotifyCollectionChanged, INotifyPropertyChanged
2123
{
2224
//------------------------------------------------------
@@ -238,10 +240,16 @@ protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
238240
NotifyCollectionChangedEventHandler handler = CollectionChanged;
239241
if (handler != null)
240242
{
241-
using (BlockReentrancy())
243+
// Not calling BlockReentrancy() here to avoid the SimpleMonitor allocation.
244+
_blockReentrancyCount++;
245+
try
242246
{
243247
handler(this, e);
244248
}
249+
finally
250+
{
251+
_blockReentrancyCount--;
252+
}
245253
}
246254
}
247255

@@ -260,16 +268,16 @@ protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
260268
/// </remarks>
261269
protected IDisposable BlockReentrancy()
262270
{
263-
_monitor.Enter();
264-
return _monitor;
271+
_blockReentrancyCount++;
272+
return EnsureMonitorInitialized();
265273
}
266274

267275
/// <summary> Check and assert for reentrant attempts to change this collection. </summary>
268276
/// <exception cref="InvalidOperationException"> raised when changing the collection
269277
/// while another collection change is still being notified to other listeners </exception>
270278
protected void CheckReentrancy()
271279
{
272-
if (_monitor.Busy)
280+
if (_blockReentrancyCount > 0)
273281
{
274282
// we can allow changes if there's only one listener - the problem
275283
// only arises if reentrant changes make the original event args
@@ -337,6 +345,25 @@ private void OnCollectionReset()
337345
{
338346
OnCollectionChanged(EventArgsCache.ResetCollectionChanged);
339347
}
348+
349+
private SimpleMonitor EnsureMonitorInitialized()
350+
{
351+
return _monitor ?? (_monitor = new SimpleMonitor(this));
352+
}
353+
354+
[OnSerializing]
355+
private void OnSerializing(StreamingContext context)
356+
{
357+
EnsureMonitorInitialized();
358+
_monitor._busyCount = _blockReentrancyCount;
359+
}
360+
361+
[OnDeserialized]
362+
private void OnDeserialized(StreamingContext context)
363+
{
364+
_blockReentrancyCount = _monitor._busyCount;
365+
_monitor._collection = this;
366+
}
340367
#endregion Private Methods
341368

342369
//------------------------------------------------------
@@ -349,21 +376,24 @@ private void OnCollectionReset()
349376

350377
// this class helps prevent reentrant calls
351378
[Serializable]
352-
private class SimpleMonitor : IDisposable
379+
[System.Runtime.CompilerServices.TypeForwardedFrom("WindowsBase, Version=3.0.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")]
380+
private sealed class SimpleMonitor : IDisposable
353381
{
354-
public void Enter()
382+
internal int _busyCount; // Only used during (de)serialization to maintain compatibility with desktop.
383+
384+
[NonSerialized]
385+
internal ObservableCollection<T> _collection;
386+
387+
public SimpleMonitor(ObservableCollection<T> collection)
355388
{
356-
++_busyCount;
389+
Debug.Assert(collection != null);
390+
_collection = collection;
357391
}
358392

359393
public void Dispose()
360394
{
361-
--_busyCount;
395+
_collection._blockReentrancyCount--;
362396
}
363-
364-
public bool Busy { get { return _busyCount > 0; } }
365-
366-
int _busyCount;
367397
}
368398

369399
#endregion Private Types
@@ -376,7 +406,10 @@ public void Dispose()
376406

377407
#region Private Fields
378408

379-
private readonly SimpleMonitor _monitor = new SimpleMonitor();
409+
private SimpleMonitor _monitor; // Lazily allocated only when a subclass calls BlockReentrancy() or during serialization.
410+
411+
[NonSerialized]
412+
private int _blockReentrancyCount;
380413
#endregion Private Fields
381414
}
382415

0 commit comments

Comments
 (0)