/// <summary> /// Remove dead entries from the data for the given source. Returns true if /// some entries were actually removed. /// </summary> protected virtual bool Purge(object source, object data, bool purgeAll) { bool foundDirt = false; bool removeList = purgeAll || source == null; // remove dead entries from the list if (!removeList) { ListenerList list = (ListenerList)data; if (ListenerList.PrepareForWriting(ref list) && source != null) { Table[this, source] = list; } if (list.Purge()) { foundDirt = true; } removeList = list.IsEmpty; } // if the list is no longer needed, stop listening to the event if (removeList) { if (source != null) // source may have been GC'd { StopListening(source); // remove the list completely (in the purgeAll case, we'll do it later) if (!purgeAll) { Table.Remove(this, source); foundDirt = true; } } } return(foundDirt); }
/// <summary> /// Remove dead entries from the data for the given source. Returns true if /// some entries were actually removed. /// </summary> protected override bool Purge(object source, object data, bool purgeAll) { bool foundDirt = false; if (!purgeAll) { HybridDictionary dict = (HybridDictionary)data; int ignoredKeys = 0; if (!BaseAppContextSwitches.EnableWeakEventMemoryImprovements) { // copy the keys into a separate array, so that later on // we can change the dictionary while iterating over the keys ICollection ic = dict.Keys; String[] keys = new String[ic.Count]; ic.CopyTo(keys, 0); for (int i = keys.Length - 1; i >= 0; --i) { if (keys[i] == AllListenersKey) { ++ignoredKeys; continue; // ignore the special entry for now } // for each key, remove dead entries in its list bool removeList = purgeAll || source == null; if (!removeList) { ListenerList list = (ListenerList)dict[keys[i]]; if (ListenerList.PrepareForWriting(ref list)) { dict[keys[i]] = list; } if (list.Purge()) { foundDirt = true; } removeList = (list.IsEmpty); } // if there are no more entries, remove the key if (removeList) { dict.Remove(keys[i]); } } #if WeakEventTelemetry LogAllocation(ic.GetType(), 1, 12); // dict.Keys - Hashtable+KeyCollection LogAllocation(typeof(String[]), 1, 12 + ic.Count * 4); // keys #endif } else { Debug.Assert(_toRemove.Count == 0, "to-remove list should be empty"); // If an "in-use" list is changed, we will re-install its clone // back into the dictionary. Doing this inside the loop // causes an exception "collection was modified after the enumerator // was instantiated" (DDVSO 812614), so instead just record // what to do and do the actual work after the loop. // This is a rare case - it only arises if a PropertyChanged event // handler calls (indirectly) into the cleanup code - so allocate // the temporary memory lazily on the stack. // [In the bug, the indirect call comes about because the app's // event handler calls ShowDialog(), which pushes a dispatcher // frame to run a nested message pump. Then a pending cleanup task // reaches the front of the dispatcher queue before the dialog // is dismissed, effectively calling this method while the // PropertyChanged event delivery is in progress.] HybridDictionary toInstall = null; // enumerate the dictionary using IDE explicitly rather than // foreach, to avoid allocating temporary DictionaryEntry objects IDictionaryEnumerator ide = dict.GetEnumerator() as IDictionaryEnumerator; while (ide.MoveNext()) { String key = (String)ide.Key; if (key == AllListenersKey) { ++ignoredKeys; continue; // ignore the special entry for now } // for each key, remove dead entries in its list bool removeList = purgeAll || source == null; if (!removeList) { ListenerList list = (ListenerList)ide.Value; bool inUse = ListenerList.PrepareForWriting(ref list); bool isChanged = false; if (list.Purge()) { isChanged = true; foundDirt = true; } removeList = (list.IsEmpty); // if a cloned list changed, remember the details // so that the clone can be installed back into the // dictionary outside the iteration (DDVSO 812614) if (!removeList && inUse && isChanged) { if (toInstall == null) { // lazy allocation toInstall = new HybridDictionary(); } toInstall[key] = list; } } // if there are no more entries, remove the key if (removeList) { _toRemove.Add(key); } } // do the actual removal (outside the dictionary iteration) if (_toRemove.Count > 0) { foreach (String key in _toRemove) { dict.Remove(key); } _toRemove.Clear(); _toRemove.TrimExcess(); } // do the actual re-install of "in-use" lists that changed if (toInstall != null) { IDictionaryEnumerator installDE = toInstall.GetEnumerator() as IDictionaryEnumerator; while (installDE.MoveNext()) { String key = (String)installDE.Key; ListenerList list = (ListenerList)installDE.Value; dict[key] = list; } } #if WeakEventTelemetry Type enumeratorType = ide.GetType(); if (enumeratorType.Name.IndexOf("NodeEnumerator") >= 0) { LogAllocation(enumeratorType, 1, 24); // ListDictionary+NodeEnumerator } else { LogAllocation(enumeratorType, 1, 36); // Hashtable+HashtableEnumerator } #endif } if (dict.Count == ignoredKeys) { // if there are no more listeners at all, remove the entry from // the main table, and prepare to stop listening purgeAll = true; if (source != null) // source may have been GC'd { this.Remove(source); } } else if (foundDirt) { // if any entries were purged, invalidate the special entry dict.Remove(AllListenersKey); _proposedAllListenersList = null; } } if (purgeAll) { // stop listening. List cleanup is handled by Purge() if (source != null) // source may have been GC'd { StopListening(source); } foundDirt = true; } return(foundDirt); }
/// <summary> /// Remove dead entries from the data for the given source. Returns true if /// some entries were actually removed. /// </summary> protected override bool Purge(object source, object data, bool purgeAll) { bool foundDirt = false; if (!purgeAll) { HybridDictionary dict = (HybridDictionary)data; // copy the keys into a separate array, so that later on // we can change the dictionary while iterating over the keys ICollection ic = dict.Keys; String[] keys = new String[ic.Count]; ic.CopyTo(keys, 0); for (int i = keys.Length - 1; i >= 0; --i) { if (keys[i] == AllListenersKey) { continue; // ignore the special entry for now } // for each key, remove dead entries in its list bool removeList = purgeAll || source == null; if (!removeList) { ListenerList list = (ListenerList)dict[keys[i]]; if (ListenerList.PrepareForWriting(ref list)) { dict[keys[i]] = list; } if (list.Purge()) { foundDirt = true; } removeList = (list.IsEmpty); } // if there are no more entries, remove the key if (removeList) { dict.Remove(keys[i]); } } if (dict.Count == 0) { // if there are no more listeners at all, remove the entry from // the main table, and prepare to stop listening purgeAll = true; if (source != null) // source may have been GC'd { this.Remove(source); } } else if (foundDirt) { // if any entries were purged, invalidate the special entry dict.Remove(AllListenersKey); _proposedAllListenersList = null; } } if (purgeAll) { // stop listening. List cleanup is handled by Purge() if (source != null) // source may have been GC'd { StopListening(source); } foundDirt = true; } return(foundDirt); }
internal bool Purge() { ListenerList.PrepareForWriting(ref _listeners); return(_listeners.Purge()); }
/// <summary> /// Remove dead entries from the data for the given source. Returns true if /// some entries were actually removed. /// </summary> protected override bool Purge(object source, object data, bool purgeAll) { bool foundDirt = false; if (!purgeAll) { HybridDictionary dict = (HybridDictionary)data; int ignoredKeys = 0; if (!BaseAppContextSwitches.EnableWeakEventMemoryImprovements) { // copy the keys into a separate array, so that later on // we can change the dictionary while iterating over the keys ICollection ic = dict.Keys; String[] keys = new String[ic.Count]; ic.CopyTo(keys, 0); for (int i = keys.Length - 1; i >= 0; --i) { if (keys[i] == AllListenersKey) { ++ignoredKeys; continue; // ignore the special entry for now } // for each key, remove dead entries in its list bool removeList = purgeAll || source == null; if (!removeList) { ListenerList list = (ListenerList)dict[keys[i]]; if (ListenerList.PrepareForWriting(ref list)) { dict[keys[i]] = list; } if (list.Purge()) { foundDirt = true; } removeList = (list.IsEmpty); } // if there are no more entries, remove the key if (removeList) { dict.Remove(keys[i]); } } #if WeakEventTelemetry LogAllocation(ic.GetType(), 1, 12); // dict.Keys - Hashtable+KeyCollection LogAllocation(typeof(String[]), 1, 12 + ic.Count * 4); // keys #endif } else { Debug.Assert(_toRemove.Count == 0, "to-remove list should be empty"); // enumerate the dictionary using IDE explicitly rather than // foreach, to avoid allocating temporary DictionaryEntry objects IDictionaryEnumerator ide = dict.GetEnumerator() as IDictionaryEnumerator; while (ide.MoveNext()) { String key = (String)ide.Key; if (key == AllListenersKey) { ++ignoredKeys; continue; // ignore the special entry for now } // for each key, remove dead entries in its list bool removeList = purgeAll || source == null; if (!removeList) { ListenerList list = (ListenerList)ide.Value; if (ListenerList.PrepareForWriting(ref list)) { dict[key] = list; } if (list.Purge()) { foundDirt = true; } removeList = (list.IsEmpty); } // if there are no more entries, remove the key if (removeList) { _toRemove.Add(key); } } // do the actual removal (outside the dictionary iteration) if (_toRemove.Count > 0) { foreach (String key in _toRemove) { dict.Remove(key); } _toRemove.Clear(); _toRemove.TrimExcess(); } #if WeakEventTelemetry Type enumeratorType = ide.GetType(); if (enumeratorType.Name.IndexOf("NodeEnumerator") >= 0) { LogAllocation(enumeratorType, 1, 24); // ListDictionary+NodeEnumerator } else { LogAllocation(enumeratorType, 1, 36); // Hashtable+HashtableEnumerator } #endif } if (dict.Count == ignoredKeys) { // if there are no more listeners at all, remove the entry from // the main table, and prepare to stop listening purgeAll = true; if (source != null) // source may have been GC'd { this.Remove(source); } } else if (foundDirt) { // if any entries were purged, invalidate the special entry dict.Remove(AllListenersKey); _proposedAllListenersList = null; } } if (purgeAll) { // stop listening. List cleanup is handled by Purge() if (source != null) // source may have been GC'd { StopListening(source); } foundDirt = true; } return(foundDirt); }