diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/ResourceDictionary.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/ResourceDictionary.cs index 390918a439c..00c3e73fcf4 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/ResourceDictionary.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/ResourceDictionary.cs @@ -1735,23 +1735,31 @@ private object FetchResource( DeferredResourceReference deferredResourceReference; if (!IsThemeDictionary) { - if (_ownerApps != null) - { - deferredResourceReference = new DeferredAppResourceReference(this, resourceKey); - } - else - { - deferredResourceReference = new DeferredResourceReference(this, resourceKey); - } - // Cache the deferredResourceReference so that it can be validated // in case of a dictionary change prior to its inflation if (_deferredResourceReferences == null) { - _deferredResourceReferences = new WeakReferenceList(); + _deferredResourceReferences = new DeferredResourceReferenceList(); + } + + if (_deferredResourceReferences.Get(resourceKey) is { } existingDeferredResourceReference + && existingDeferredResourceReference.Dictionary == this) + { + deferredResourceReference = existingDeferredResourceReference; } + else + { + if (_ownerApps != null) + { + deferredResourceReference = new DeferredAppResourceReference(this, resourceKey); + } + else + { + deferredResourceReference = new DeferredResourceReference(this, resourceKey); + } - _deferredResourceReferences.Add( deferredResourceReference, true /*SkipFind*/); + _deferredResourceReferences.AddOrSet(deferredResourceReference); + } } else { @@ -1775,22 +1783,18 @@ private void ValidateDeferredResourceReferences(object resourceKey) { if (_deferredResourceReferences != null) { - foreach (Object o in _deferredResourceReferences) - { + DeferredResourceReference deferredResourceReference = _deferredResourceReferences.Get(resourceKey); - DeferredResourceReference deferredResourceReference = o as DeferredResourceReference; - if (deferredResourceReference != null && (resourceKey == null || Object.Equals(resourceKey, deferredResourceReference.Key))) - { - // This will inflate the deferred reference, causing it - // to be removed from the list. The list may also be - // purged of dead references. - deferredResourceReference.GetValue(BaseValueSourceInternal.Unknown); - } + if (deferredResourceReference is not null) + { + // This will inflate the deferred reference, causing it + // to be removed from the list. The list may also be + // purged of dead references. + deferredResourceReference.GetValue(BaseValueSourceInternal.Unknown); } } } - /// /// Called when the MergedDictionaries collection changes /// @@ -2053,7 +2057,7 @@ internal WeakReferenceList ApplicationOwners #region Properties - internal WeakReferenceList DeferredResourceReferences + internal DeferredResourceReferenceList DeferredResourceReferences { get { return _deferredResourceReferences; } } @@ -2478,10 +2482,7 @@ private void MoveDeferredResourceReferencesFrom(ResourceDictionary loadedRD) // redirect each entry toward its new owner if (_deferredResourceReferences != null) { - foreach (DeferredResourceReference drr in _deferredResourceReferences) - { - drr.Dictionary = this; - } + _deferredResourceReferences.ChangeDictionary(this); } } @@ -2548,7 +2549,7 @@ private enum FallbackState private WeakReferenceList _ownerFEs = null; private WeakReferenceList _ownerFCEs = null; private WeakReferenceList _ownerApps = null; - private WeakReferenceList _deferredResourceReferences = null; + private DeferredResourceReferenceList _deferredResourceReferences = null; private ObservableCollection _mergedDictionaries = null; private Uri _source = null; private Uri _baseUri = null; diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/SystemResources.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/SystemResources.cs index 6246ee697f6..4a6622b9d01 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/SystemResources.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/SystemResources.cs @@ -1739,8 +1739,9 @@ internal override object GetValue(BaseValueSourceInternal valueSource) { // Note that we are replacing the _keyorValue field // with the value and deleting the _dictionary field. - _keyOrValue = value; RemoveFromDictionary(); + // Update after removal from dictionary as we need the key for proper removal + _keyOrValue = value; } // Freeze if this value originated from a style or template @@ -2032,9 +2033,103 @@ internal override bool IsUnset #endregion Properties } -} + internal class DeferredResourceReferenceList + { + private readonly object _syncRoot = new(); + private readonly Dictionary> _entries = new(); + private int _potentiallyDeadEntryCount = 0; + + public void AddOrSet(DeferredResourceReference deferredResourceReference) + { + lock (_syncRoot) + { + _entries[deferredResourceReference.Key] = new WeakReference(deferredResourceReference); + } + } + + public void Remove(DeferredResourceReference deferredResourceReference) + { + lock (_syncRoot) + { + _entries.Remove(deferredResourceReference.Key); + } + } + + internal DeferredResourceReference Get(object resourceKey) + { + lock (_syncRoot) + { + _entries.TryGetValue(resourceKey, out var weakReference); + + if (weakReference is null) + { + return null; + } + + if (weakReference.TryGetTarget(out var deferredResourceReference)) + { + return deferredResourceReference; + } + else + { + ++_potentiallyDeadEntryCount; + } + } + + PurgeIfRequired(); + + return null; + } + + internal void ChangeDictionary(ResourceDictionary resourceDictionary) + { + lock (_syncRoot) + { + foreach (WeakReference weakReference in _entries.Values) + { + if (weakReference.TryGetTarget(out var deferredResourceReference)) + { + deferredResourceReference.Dictionary = resourceDictionary; + } + else + { + ++_potentiallyDeadEntryCount; + } + } + } + PurgeIfRequired(); + } + + private void PurgeIfRequired() + { + if (_potentiallyDeadEntryCount > 25) + { + Purge(); + } + } + private void Purge() + { + lock (_syncRoot) + { + List deadKeys = new(Math.Min(_potentiallyDeadEntryCount, _entries.Count)); + _potentiallyDeadEntryCount = 0; + foreach (KeyValuePair> entry in _entries) + { + if (entry.Value.TryGetTarget(out _) == false) + { + deadKeys.Add(entry.Key); + } + } + foreach (object deadKey in deadKeys) + { + _entries.Remove(deadKey); + } + } + } + } +}