Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimized work with Listener list #308

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
124 changes: 124 additions & 0 deletions src/Engine/ListenerList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
namespace WPFLocalizeExtension.Engine
{
#region Usings

using System;
using System.Collections.Generic;
using System.Linq;

#endregion

/// <summary>
/// Represents a collection of listeners.
/// </summary>
internal class ListenersList
{
private readonly Dictionary<WeakReference, int> listeners;
private readonly Dictionary<int, List<WeakReference>> listenersHashCodes;
private readonly List<WeakReference> deadListeners;

/// <summary>
/// Create new empty <see cref="ListenersList" /> instance.
/// </summary>
public ListenersList()
{
listeners = new Dictionary<WeakReference, int>();
listenersHashCodes = new Dictionary<int, List<WeakReference>>();
deadListeners = new List<WeakReference>();
}

/// <summary>
/// The count of listeners.
/// </summary>
public int Count => listeners.Count;

/// <summary>
/// Add new listener.
/// </summary>
public void AddListener(IDictionaryEventListener listener)
{
// Add listener if it not registered yet.
var weakReference = new WeakReference(listener);
var hashCode = listener.GetHashCode();
if (!listenersHashCodes.TryGetValue(hashCode, out var sameHashCodeListeners))
{
listeners.Add(weakReference, hashCode);
listenersHashCodes.Add(hashCode, new List<WeakReference> { weakReference });
}
else if (sameHashCodeListeners.All(wr => wr.Target != listener))
{
listeners.Add(weakReference, hashCode);
sameHashCodeListeners.Add(weakReference);
}
}

/// <summary>
/// Get all alive listeners.
/// </summary>
public IEnumerable<IDictionaryEventListener> GetListeners()
{
try
{
foreach (var listener in listeners)
{
var listenerReference = listener.Key.Target as IDictionaryEventListener;
if (listenerReference == null)
{
deadListeners.Add(listener.Key);
continue;
}

yield return listenerReference;
}
}
finally
{
// Finally block is necessary because of `yield return`.
// It guarantees this method will be called even if listeners won't enumerate till the end.
ClearDeadReferences();
}
}

/// <summary>
/// Remove listener.
/// </summary>
public void RemoveListener(IDictionaryEventListener listener)
{
var hashCode = listener.GetHashCode();
if (!listenersHashCodes.TryGetValue(hashCode, out var hashCodes))
return;

var wr = hashCodes.FirstOrDefault(l => l.Target == listener);
if (wr == null)
return;

if (hashCodes.Count > 1)
hashCodes.Remove(wr);
else
listenersHashCodes.Remove(hashCode);

listeners.Remove(wr);
}

/// <summary>
/// Clear internal list from all dead listeners.
/// </summary>
private void ClearDeadReferences()
{
if (deadListeners.Count == 0)
return;

foreach (var deadListener in deadListeners)
konne marked this conversation as resolved.
Show resolved Hide resolved
{
var hashCode = listeners[deadListener];
listenersHashCodes[hashCode].Remove(deadListener);
if (!listenersHashCodes[hashCode].Any())
listenersHashCodes.Remove(hashCode);

listeners.Remove(deadListener);
}

deadListeners.Clear();
}
}
}
63 changes: 20 additions & 43 deletions src/Engine/LocalizeDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -969,7 +969,7 @@ internal static class DictionaryEvent
/// <summary>
/// The list of listeners
/// </summary>
private static readonly List<WeakReference> Listeners = new List<WeakReference>();
private static readonly ListenersList Listeners = new ListenersList();
private static readonly object ListenersLock = new object();

/// <summary>
Expand All @@ -979,22 +979,25 @@ internal static class DictionaryEvent
/// <param name="args">The event arguments.</param>
internal static void Invoke(DependencyObject sender, DictionaryEventArgs args)
{
var list = new List<IDictionaryEventListener>();

lock (ListenersLock)
{
foreach (var wr in Listeners.ToList())
var exceptions = new List<Exception>();

foreach (var listener in Listeners.GetListeners())
{
var targetReference = wr.Target;
if (targetReference != null)
list.Add((IDictionaryEventListener)targetReference);
else
Listeners.Remove(wr);
try
{
listener.ResourceChanged(sender, args);
}
catch (Exception e)
{
exceptions.Add(e);
}
}
}

foreach (var item in list)
item.ResourceChanged(sender, args);
if (exceptions.Count > 0)
throw new AggregateException(exceptions);
}
}

/// <summary>
Expand All @@ -1005,24 +1008,10 @@ internal static void AddListener(IDictionaryEventListener listener)
{
if (listener == null)
return;

// Check, if this listener already was added.
bool listenerExists = false;


lock (ListenersLock)
{
foreach (var wr in Listeners.ToList())
{
var targetReference = wr.Target;
if (targetReference == null)
Listeners.Remove(wr);
else if (targetReference == listener)
listenerExists = true;
}

// Add it now.
if (!listenerExists)
Listeners.Add(new WeakReference(listener));
Listeners.AddListener(listener);
}
}

Expand All @@ -1037,14 +1026,7 @@ internal static void RemoveListener(IDictionaryEventListener listener)

lock (ListenersLock)
{
foreach (var wr in Listeners.ToList())
{
var targetReference = wr.Target;
if (targetReference == null)
Listeners.Remove(wr);
else if ((IDictionaryEventListener)targetReference == listener)
Listeners.Remove(wr);
}
Listeners.RemoveListener(listener);
}
}

Expand All @@ -1057,14 +1039,9 @@ internal static IEnumerable<T> EnumerateListeners<T>()
{
lock (ListenersLock)
{
foreach (var wr in Listeners.ToList())
foreach (var listener in Listeners.GetListeners().OfType<T>())
{
var targetReference = wr.Target;

if (targetReference == null)
Listeners.Remove(wr);
else if (targetReference is T)
yield return (T)targetReference;
yield return listener;
}
}
}
Expand Down