Skip to content

Commit

Permalink
Merge pull request #42 from thomaslevesque/handler-exception-handling
Browse files Browse the repository at this point in the history
Handle exceptions thrown by handlers
  • Loading branch information
thomaslevesque authored Nov 15, 2019
2 parents 852a9a0 + d661b0f commit 1649193
Show file tree
Hide file tree
Showing 10 changed files with 677 additions and 49 deletions.
82 changes: 69 additions & 13 deletions src/WeakEvent/AsyncWeakEventSource.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Reflection;
using System.Threading.Tasks;
using static WeakEvent.WeakEventSourceHelper;

Expand Down Expand Up @@ -37,6 +38,61 @@ public async Task RaiseAsync(object? sender, TEventArgs args)
}
}

/// <summary>
/// Raises the event by invoking each handler that hasn't been garbage collected. Exceptions thrown by
/// individual handlers are passed to the specified <c>exceptionHandler</c> to decide what to do with them.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="args">An object that contains the event data.</param>
/// <param name="exceptionHandler">A delegate that processes exceptions thrown by individual handlers.</param>
/// <remarks>The handlers are invoked one after the other, in the order they were subscribed in.
/// Each handler is awaited before invoking the next one.</remarks>
public async Task RaiseAsync(object? sender, TEventArgs args, Func<Exception, ExceptionHandlingFlags> exceptionHandler)
{
if (exceptionHandler is null) throw new ArgumentNullException(nameof(exceptionHandler));
var validHandlers = GetValidHandlers(_handlers);
foreach (var handler in validHandlers)
{
try
{
await handler.Invoke(sender, args);
}
catch (Exception ex) when (HandleException(_handlers, handler, exceptionHandler(ex)))
{
}
}
}

/// <summary>
/// Raises the event by invoking each handler that hasn't been garbage collected. Exceptions thrown by
/// individual handlers are passed to the specified <c>exceptionHandler</c> to decide what to do with them.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="args">An object that contains the event data.</param>
/// <param name="exceptionHandler">A delegate that processes exceptions thrown by individual handlers.</param>
/// <remarks>The handlers are invoked one after the other, in the order they were subscribed in.
/// Each handler is awaited before invoking the next one.</remarks>
public async Task RaiseAsync(object? sender, TEventArgs args, Func<Exception, Task<ExceptionHandlingFlags>> exceptionHandler)
{
if (exceptionHandler is null) throw new ArgumentNullException(nameof(exceptionHandler));
var validHandlers = GetValidHandlers(_handlers);
foreach (var handler in validHandlers)
{
try
{
await handler.Invoke(sender, args);
}
catch (Exception ex)
{
var flags = await exceptionHandler(ex);
if (!HandleException(_handlers, handler, flags))
{
throw;
}
}
}
}

/// <summary>
/// Adds an event handler.
/// </summary>
Expand All @@ -54,8 +110,7 @@ public void Subscribe(AsyncEventHandler<TEventArgs> handler)
/// <param name="handler">The handler to subscribe.</param>
/// <remarks>Only a weak reference to the handler's <c>Target</c> is kept, so that it can be garbage collected.
/// However, as long as the <c>lifetime</c> object is alive, the handler will be kept alive. This is useful for
/// subscribing with anonymous methods (e.g. lambda expressions). Note that you must specify the same lifetime
/// object to unsubscribe, otherwise the handler's lifetime will remain tied to the lifetime object.</remarks>
/// subscribing with anonymous methods (e.g. lambda expressions).</remarks>
public void Subscribe(object? lifetimeObject, AsyncEventHandler<TEventArgs> handler)
{
Subscribe<DelegateCollection, OpenEventHandler, StrongHandler>(lifetimeObject, ref _handlers, handler);
Expand All @@ -69,7 +124,7 @@ public void Subscribe(object? lifetimeObject, AsyncEventHandler<TEventArgs> hand
/// of the handler's invocation list is removed. If the exact invocation list is not found, nothing is removed.</remarks>
public void Unsubscribe(AsyncEventHandler<TEventArgs> handler)
{
Unsubscribe(null, handler);
Unsubscribe<OpenEventHandler, StrongHandler>(_handlers, handler);
}

/// <summary>
Expand All @@ -79,34 +134,35 @@ public void Unsubscribe(AsyncEventHandler<TEventArgs> handler)
/// <param name="handler">The handler to unsubscribe.</param>
/// <remarks>The behavior is the same as that of <see cref="Delegate.Remove(Delegate, Delegate)"/>. Only the last instance
/// of the handler's invocation list is removed. If the exact invocation list is not found, nothing is removed.</remarks>
[Obsolete("This method is obsolete and will be removed in a future version. Use the Unsubscribe overload that doesn't take a lifetime object instead.")]
public void Unsubscribe(object? lifetimeObject, AsyncEventHandler<TEventArgs> handler)
{
Unsubscribe<OpenEventHandler, StrongHandler>(lifetimeObject, _handlers, handler);
Unsubscribe(handler);
}

internal delegate Task OpenEventHandler(object? target, object? sender, TEventArgs e);

internal struct StrongHandler
internal struct StrongHandler : IStrongHandler<OpenEventHandler, StrongHandler>
{
private readonly object? _target;
private readonly OpenEventHandler _openHandler;

public StrongHandler(object? target, OpenEventHandler openHandler)
public StrongHandler(object? target, WeakDelegate<OpenEventHandler, StrongHandler> weakHandler)
{
_target = target;
_openHandler = openHandler;
Target = target;
WeakHandler = weakHandler;
}

public object? Target { get; }
public WeakDelegate<OpenEventHandler, StrongHandler> WeakHandler { get; }

public Task Invoke(object? sender, TEventArgs e)
{
return _openHandler(_target, sender, e);
return WeakHandler.OpenHandler(Target, sender, e);
}
}

internal class DelegateCollection : DelegateCollectionBase<OpenEventHandler, StrongHandler>
{
public DelegateCollection()
: base((target, openHandler) => new StrongHandler(target, openHandler))
: base((target, weakHandler) => new StrongHandler(target, weakHandler))
{
}
}
Expand Down
48 changes: 40 additions & 8 deletions src/WeakEvent/DelegateCollectionBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace WeakEvent
{
internal abstract class DelegateCollectionBase<TOpenEventHandler, TStrongHandler>
where TOpenEventHandler : Delegate
where TStrongHandler : struct
where TStrongHandler : struct, IStrongHandler<TOpenEventHandler, TStrongHandler>
{
#region Open handler generation and cache

Expand Down Expand Up @@ -75,9 +75,9 @@ private static TOpenEventHandler CreateOpenHandler(MethodInfo method)

private ConditionalWeakTable<object, List<object>>? _targetLifetimes;

private readonly Func<object?, TOpenEventHandler, TStrongHandler> _createStrongHandler;
private readonly StrongHandlerFactory<TOpenEventHandler, TStrongHandler> _createStrongHandler;

protected DelegateCollectionBase(Func<object?, TOpenEventHandler, TStrongHandler> createStrongHandler)
protected DelegateCollectionBase(StrongHandlerFactory<TOpenEventHandler, TStrongHandler> createStrongHandler)
{
_delegates = new List<WeakDelegate<TOpenEventHandler, TStrongHandler>?>();
_index = new Dictionary<int, List<int>>();
Expand All @@ -89,7 +89,7 @@ public void Add(object? lifetimeObject, Delegate[] invocationList)
foreach (var singleHandler in invocationList)
{
var openHandler = OpenHandlerCache.GetOrAdd(singleHandler.GetMethodInfo(), CreateOpenHandler);
_delegates.Add(new WeakDelegate<TOpenEventHandler, TStrongHandler>(singleHandler, openHandler, _createStrongHandler));
_delegates.Add(new WeakDelegate<TOpenEventHandler, TStrongHandler>(lifetimeObject, singleHandler, openHandler, _createStrongHandler));
var index = _delegates.Count - 1;
AddToIndex(singleHandler, index);
KeepTargetAlive(lifetimeObject, singleHandler.Target);
Expand All @@ -104,7 +104,7 @@ public void Add(object? lifetimeObject, Delegate[] invocationList)
/// <remarks>
/// Follows the same logic as MulticastDelegate.Remove.
/// </remarks>
public void Remove(object? lifetimeObject, Delegate[] invocationList)
public void Remove(Delegate[] invocationList)
{
int matchIndex = GetIndexOfInvocationListLastOccurrence(invocationList);

Expand All @@ -115,6 +115,7 @@ public void Remove(object? lifetimeObject, Delegate[] invocationList)
{
var singleHandler = invocationList[invocationIndex];
var index = matchIndex + invocationIndex;
var weakDelegate = _delegates[index];
_delegates[index] = null;
var hashCode = GetDelegateHashCode(singleHandler);
if (_index.TryGetValue(hashCode, out var indices))
Expand All @@ -127,7 +128,30 @@ public void Remove(object? lifetimeObject, Delegate[] invocationList)
}

_deletedCount++;
StopKeepingTargetAlive(lifetimeObject, singleHandler.Target);
StopKeepingTargetAlive(weakDelegate?.LifetimeObject, singleHandler.Target);
}
}

public void Remove(TStrongHandler handler)
{
for (int i = 0; i < _delegates.Count; i++)
{
var @delegate = _delegates[i];
if (@delegate is {} && @delegate.IsMatch(handler))
{
_delegates[i] = null;
var hashCode = GetStrongHandlerHashCode(handler);
if (_index.TryGetValue(hashCode, out var indices))
{
int lastIndex = indices.LastIndexOf(i);
if (lastIndex >= 0)
{
indices.RemoveAt(lastIndex);
}
}
_deletedCount++;
StopKeepingTargetAlive(handler.WeakHandler.LifetimeObject, handler.Target);
}
}
}

Expand Down Expand Up @@ -234,6 +258,14 @@ private static int GetDelegateHashCode(Delegate handler)
return hashCode;
}

private static int GetStrongHandlerHashCode(TStrongHandler handler)
{
var hashCode = -335093136;
hashCode = hashCode * -1521134295 + (handler.Target?.GetHashCode()).GetValueOrDefault();
hashCode = hashCode * -1521134295 + (handler.WeakHandler.Method?.GetHashCode()).GetValueOrDefault();
return hashCode;
}

private void AddToIndex(Delegate singleHandler, int index)
{
var hashCode = GetDelegateHashCode(singleHandler);
Expand All @@ -258,10 +290,10 @@ private void KeepTargetAlive(object? lifetimeObject, object? target)

private void StopKeepingTargetAlive(object? lifetimeObject, object? target)
{
if (lifetimeObject is null || target is null || lifetimeObject == target)
if (_targetLifetimes is null)
return;

if (_targetLifetimes is null)
if (lifetimeObject is null || target is null || lifetimeObject == target)
return;

if (_targetLifetimes.TryGetValue(lifetimeObject, out var targets))
Expand Down
20 changes: 20 additions & 0 deletions src/WeakEvent/ExceptionHandlingFlags.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;

namespace WeakEvent
{
/// <summary>
/// Flags that specify what to do with exceptions thrown by individual event handlers.
/// An exception handler passed to <c>Raise</c> or <c>RaiseAsync</c> can return any combination
/// of thse flags.
/// </summary>
[Flags]
public enum ExceptionHandlingFlags
{
/// <summary>Do nothing.</summary>
None = 0,
/// <summary>Mark the exception as handled. If this flag isn't set, the exception will be rethrown.</summary>
Handled = 1,
/// <summary>Unsubscribe the handler that caused the exception.</summary>
Unsubscribe = 2
}
}
12 changes: 12 additions & 0 deletions src/WeakEvent/IStrongHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

namespace WeakEvent
{
internal interface IStrongHandler<TOpenEventHandler, TStrongHandler>
where TOpenEventHandler : Delegate
where TStrongHandler : struct, IStrongHandler<TOpenEventHandler, TStrongHandler>
{
object? Target { get; }
WeakDelegate<TOpenEventHandler, TStrongHandler> WeakHandler { get; }
}
}
8 changes: 8 additions & 0 deletions src/WeakEvent/StrongHandlerFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System;

namespace WeakEvent
{
internal delegate TStrongHandler StrongHandlerFactory<TOpenEventHandler, TStrongHandler>(object? target, WeakDelegate<TOpenEventHandler, TStrongHandler> weakHandler)
where TOpenEventHandler : Delegate
where TStrongHandler : struct, IStrongHandler<TOpenEventHandler, TStrongHandler>;
}
29 changes: 20 additions & 9 deletions src/WeakEvent/WeakDelegate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,31 @@ namespace WeakEvent
{
internal class WeakDelegate<TOpenEventHandler, TStrongHandler>
where TOpenEventHandler : Delegate
where TStrongHandler : struct
where TStrongHandler : struct, IStrongHandler<TOpenEventHandler, TStrongHandler>
{
private readonly WeakReference? _weakLifetimeObject;
private readonly WeakReference? _weakTarget;
private readonly MethodInfo _method;
private readonly TOpenEventHandler _openHandler;
private readonly Func<object?, TOpenEventHandler, TStrongHandler> _createStrongHandler;
private readonly StrongHandlerFactory<TOpenEventHandler, TStrongHandler> _createStrongHandler;

public WeakDelegate(
object? lifetimeObject,
Delegate handler,
TOpenEventHandler openHandler,
Func<object?, TOpenEventHandler, TStrongHandler> createStrongHandler)
StrongHandlerFactory<TOpenEventHandler, TStrongHandler> createStrongHandler)
{
_weakLifetimeObject = lifetimeObject is {} ? new WeakReference(lifetimeObject) : null;
_weakTarget = handler.Target is {} ? new WeakReference(handler.Target) : null;
_method = handler.GetMethodInfo();
_openHandler = openHandler;
Method = handler.GetMethodInfo();
OpenHandler = openHandler;
_createStrongHandler = createStrongHandler;
}

public object? LifetimeObject => _weakLifetimeObject?.Target;
public bool IsAlive => _weakTarget?.IsAlive ?? true;

public MethodInfo Method { get; }
public TOpenEventHandler OpenHandler { get; }

public TStrongHandler? TryGetStrongHandler()
{
object? target = null;
Expand All @@ -35,13 +40,19 @@ public WeakDelegate(
return null;
}

return _createStrongHandler(target, _openHandler);
return _createStrongHandler(target, this);
}

public bool IsMatch(Delegate handler)
{
return ReferenceEquals(handler.Target, _weakTarget?.Target)
&& handler.GetMethodInfo().Equals(_method);
&& handler.GetMethodInfo().Equals(Method);
}

public bool IsMatch(TStrongHandler handler)
{
return ReferenceEquals(handler.Target, _weakTarget?.Target)
&& handler.WeakHandler.Method.Equals(Method);
}
}
}
Loading

0 comments on commit 1649193

Please sign in to comment.