@@ -1,107 +0,0 @@ | |||
using System; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using Microsoft.Extensions.Logging; | |||
namespace Cap.Consistency.EventBus | |||
{ | |||
public abstract class BackgroundWorker | |||
: IBackgroundWorker, IDisposable | |||
{ | |||
protected readonly ILogger _logger; | |||
#if FEATURE_THREAD | |||
protected Thread _dispatchThread; | |||
#else | |||
protected Task _dispatchThread; | |||
#endif | |||
protected CancellationTokenSource _cancellationTokenSource; | |||
public virtual bool IsRunning { | |||
get { | |||
return this._dispatchThread != null && | |||
#if FEATURE_THREAD | |||
this._dispatchThread.ThreadState == ThreadState.Running; | |||
#else | |||
this._dispatchThread.Status == TaskStatus.Running; | |||
#endif | |||
} | |||
} | |||
protected BackgroundWorker(ILoggerFactory loggerFactory) { | |||
this._logger = loggerFactory.CreateLogger(this.GetType().FullName); | |||
} | |||
public virtual void Start() { | |||
this.Start(false); | |||
} | |||
public virtual void Start(bool force) { | |||
if (!force) { | |||
if (this.IsRunning) { | |||
return; | |||
} | |||
} | |||
this._cancellationTokenSource = new CancellationTokenSource(); | |||
#if !FEATURE_THREAD | |||
this._dispatchThread = this.ThreadWorker(this._cancellationTokenSource.Token); | |||
#else | |||
this._dispatchThread = new Thread((userObject) => | |||
{ | |||
this.ThreadWorker(userObject).GetAwaiter().GetResult(); | |||
}) | |||
{ | |||
IsBackground = true, | |||
Name = $"{this.GetType().Name}-Thread-{Guid.NewGuid().ToString()}" | |||
}; | |||
this._dispatchThread.Start(this._cancellationTokenSource.Token); | |||
#endif | |||
} | |||
public virtual void Stop(int timeout = 2000) { | |||
Task.WaitAny(Task.Run(() => { | |||
this._cancellationTokenSource.Cancel(); | |||
while (this.IsRunning) { | |||
Task.Delay(500).GetAwaiter().GetResult(); | |||
} | |||
}), Task.Delay(timeout)); | |||
} | |||
protected virtual async Task ThreadWorker(object userObject) { | |||
this._logger.LogInformation($"Background worker {this.GetType().FullName} has been started."); | |||
var token = (CancellationToken)userObject; | |||
while (!token.IsCancellationRequested && await this.Process()) { | |||
} | |||
this._logger.LogInformation($"Background worker {this.GetType().FullName} has been stopped."); | |||
} | |||
protected abstract Task<bool> Process(); | |||
#region IDisposable | |||
// Flag: Has Dispose already been called? | |||
private bool disposed = false; | |||
// Public implementation of Dispose pattern callable by consumers. | |||
public void Dispose() { | |||
Dispose(true); | |||
GC.SuppressFinalize(this); | |||
} | |||
// Protected implementation of Dispose pattern. | |||
protected virtual void Dispose(bool disposing) { | |||
if (disposed) | |||
return; | |||
if (disposing) { | |||
// Free any other managed objects here. | |||
this.Stop(); | |||
} | |||
// Free any unmanaged objects here. | |||
disposed = true; | |||
} | |||
#endregion IDisposable | |||
} | |||
} |
@@ -1,9 +0,0 @@ | |||
namespace Cap.Consistency.EventBus | |||
{ | |||
public class BrokeredMessage | |||
{ | |||
public byte[] Body { get; set; } | |||
public string Type { get; set; } | |||
} | |||
} |
@@ -1,155 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Reflection; | |||
using Microsoft.Extensions.Logging; | |||
namespace Cap.Consistency.EventBus | |||
{ | |||
/// <summary> | |||
/// The EventBusBase class is the base class for all the IEventBus implementations. | |||
/// </summary> | |||
public abstract class EventBusBase | |||
: BackgroundWorker, IEventBus, IDisposable | |||
{ | |||
public const int DefaultMaxPendingEventNumber = 1024 * 1024; | |||
public event EventHandler<EventHandlerHolder> MessageReceieved; | |||
protected readonly object _eventHandlerLock = new object(); | |||
protected List<EventHandlerHolder> _eventHandlerList = new List<EventHandlerHolder>(); | |||
/// <summary> | |||
/// The pending event number which does not yet dispatched. | |||
/// </summary> | |||
public abstract long PendingEventNumber { get; } | |||
public virtual bool IsDispatcherEnabled { | |||
get { | |||
return base.IsRunning; | |||
} | |||
} | |||
/// <summary> | |||
/// The constructor of EventBusBase. | |||
/// </summary> | |||
/// <param name="loggerFactory"></param> | |||
protected EventBusBase(ILoggerFactory loggerFactory) | |||
: base(loggerFactory) { | |||
this._eventHandlerList = new List<EventHandlerHolder>(); | |||
} | |||
/// <summary> | |||
/// Post an event to the event bus, dispatched after the specific time. | |||
/// </summary> | |||
/// <remarks>If you do not need the event processed in the delivery order, use SimpleEventBus instead.</remarks> | |||
/// <param name="eventObject">The event object</param> | |||
/// <param name="dispatchDelay">The delay time before dispatch this event</param> | |||
public abstract void Post(object eventObject, TimeSpan dispatchDelay); | |||
/// <summary> | |||
/// Register event handlers in the handler instance. | |||
/// One handler instance may have many event handler methods. | |||
/// These methods have EventSubscriberAttribute contract and exactly one parameter. | |||
/// </summary> | |||
/// <remarks>If you do not need the event processed in the delivery order, use SimpleEventBus instead.</remarks> | |||
/// <param name="handler">The instance of event handler class</param> | |||
public void Register(object handler) { | |||
if (handler == null) { | |||
return; | |||
} | |||
var miList = handler.GetType().GetRuntimeMethods(); | |||
lock (_eventHandlerLock) { | |||
// Don't allow register multiple times. | |||
if (_eventHandlerList.Any(record => record.Handler == handler)) { | |||
return; | |||
} | |||
List<EventHandlerHolder> newList = null; | |||
foreach (var mi in miList) { | |||
var attribute = mi.GetCustomAttribute<EventSubscriberAttribute>(); | |||
if (attribute != null) { | |||
var piList = mi.GetParameters(); | |||
if (piList.Length == 1) { | |||
// OK, we got valid handler, create newList as needed | |||
if (newList == null) { | |||
newList = new List<EventHandlerHolder>(_eventHandlerList); | |||
} | |||
newList.Add(this.CreateEventHandlerHolder(handler, mi, piList[0].ParameterType)); | |||
} | |||
} | |||
} | |||
// OK, we have new handler registered | |||
if (newList != null) { | |||
_eventHandlerList = newList; | |||
} | |||
} | |||
} | |||
/// <summary> | |||
/// Unregister event handlers belong to the handler instance. | |||
/// One handler instance may have many event handler methods. | |||
/// These methods have EventSubscriberAttribute contract and exactly one parameter. | |||
/// </summary> | |||
/// <param name="handler">The instance of event handler class</param> | |||
public void Unregister(object handler) { | |||
if (handler == null) { | |||
return; | |||
} | |||
lock (_eventHandlerLock) { | |||
bool needAction = _eventHandlerList.Any(record => record.Handler == handler); | |||
if (needAction) { | |||
var newList = new List<EventHandlerHolder>(); | |||
foreach (var record in this._eventHandlerList) { | |||
if (record.Handler != handler) { | |||
newList.Add(record); | |||
} | |||
else { | |||
record.Dispose(); | |||
} | |||
} | |||
_eventHandlerList = newList; | |||
} | |||
} | |||
} | |||
protected virtual EventHandlerHolder CreateEventHandlerHolder(object handler, MethodInfo methodInfo, Type parameterType) { | |||
return new EventHandlerHolder(handler, methodInfo, parameterType); | |||
} | |||
protected virtual void OnMessageReceieved(EventHandlerHolder handler) { | |||
this.MessageReceieved?.Invoke(this, handler); | |||
} | |||
#region IDisposable | |||
// Flag: Has Dispose already been called? | |||
private bool disposed = false; | |||
// Public implementation of Dispose pattern callable by consumers. | |||
public new void Dispose() { | |||
Dispose(true); | |||
GC.SuppressFinalize(this); | |||
} | |||
// Protected implementation of Dispose pattern. | |||
protected new virtual void Dispose(bool disposing) { | |||
if (disposed) | |||
return; | |||
if (disposing) { | |||
// Free any other managed objects here. | |||
this.Stop(); | |||
} | |||
// Free any unmanaged objects here. | |||
disposed = true; | |||
} | |||
#endregion IDisposable | |||
} | |||
} |
@@ -1,23 +0,0 @@ | |||
using System; | |||
using Microsoft.Extensions.Logging; | |||
namespace Cap.Consistency.EventBus | |||
{ | |||
public class EventBusFactory | |||
: IEventBusFactory | |||
{ | |||
private readonly ILoggerFactory _loggerFactory; | |||
public EventBusFactory(ILoggerFactory loggerFactory) { | |||
this._loggerFactory = loggerFactory; | |||
} | |||
public IEventBus CreateEventBus<TEventBus>() where TEventBus : IEventBus { | |||
return this.CreateEventBus<TEventBus>(-1); | |||
} | |||
public IEventBus CreateEventBus<TEventBus>(long maxPendingEventNumber) where TEventBus : IEventBus { | |||
return Activator.CreateInstance(typeof(TEventBus), new object[] { this._loggerFactory, maxPendingEventNumber }) as IEventBus; | |||
} | |||
} | |||
} |
@@ -1,20 +0,0 @@ | |||
namespace Cap.Consistency.EventBus | |||
{ | |||
public class EventBusOptions | |||
{ | |||
public long MaxPendingEventNumber { get; set; } | |||
public int MaxPendingEventNumber32 { | |||
get { | |||
if (this.MaxPendingEventNumber < int.MaxValue) { | |||
return (int)this.MaxPendingEventNumber; | |||
} | |||
return int.MaxValue; | |||
} | |||
} | |||
public EventBusOptions() { | |||
this.MaxPendingEventNumber = EventBusBase.DefaultMaxPendingEventNumber; | |||
} | |||
} | |||
} |
@@ -1,49 +0,0 @@ | |||
using System; | |||
using System.Reflection; | |||
namespace Cap.Consistency.EventBus | |||
{ | |||
public class EventHandlerHolder | |||
: IDisposable | |||
{ | |||
public object Handler { get; } | |||
public MethodInfo MethodInfo { get; } | |||
public Type ParameterType { get; } | |||
public EventHandlerHolder(object handler, MethodInfo methodInfo, Type parameterType) { | |||
Handler = handler; | |||
MethodInfo = methodInfo; | |||
ParameterType = parameterType; | |||
} | |||
#region IDisposable | |||
// Flag: Has Dispose already been called? | |||
private bool disposed = false; | |||
// Public implementation of Dispose pattern callable by consumers. | |||
public void Dispose() { | |||
Dispose(true); | |||
GC.SuppressFinalize(this); | |||
} | |||
// Protected implementation of Dispose pattern. | |||
protected virtual void Dispose(bool disposing) { | |||
if (disposed) | |||
return; | |||
if (disposing) { | |||
// Free any other managed objects here. | |||
// | |||
} | |||
// Free any unmanaged objects here. | |||
// | |||
disposed = true; | |||
} | |||
#endregion IDisposable | |||
} | |||
} |
@@ -1,13 +0,0 @@ | |||
using System; | |||
namespace Cap.Consistency.EventBus | |||
{ | |||
/// <summary> | |||
/// The attribute class of the event handler. | |||
/// If a method have this attribue contract and exactly one parameter, then it's event handler. | |||
/// </summary> | |||
public class EventSubscriberAttribute | |||
: Attribute | |||
{ | |||
} | |||
} |
@@ -1,18 +0,0 @@ | |||
namespace Cap.Consistency.EventBus | |||
{ | |||
public interface IBackgroundWorker | |||
{ | |||
bool IsRunning { get; } | |||
/// <summary> | |||
/// Start the background worker digest loop. | |||
/// </summary> | |||
void Start(); | |||
/// <summary> | |||
/// Stop the background worker digest loop. | |||
/// </summary> | |||
/// <param name="timeout"></param> | |||
void Stop(int timeout = 2000); | |||
} | |||
} |
@@ -1,35 +0,0 @@ | |||
using System; | |||
namespace Cap.Consistency.EventBus | |||
{ | |||
/// <summary> | |||
/// The event bus interface. | |||
/// </summary> | |||
public interface IEventBus : IBackgroundWorker | |||
{ | |||
/// <summary> | |||
/// Post an event to the event bus, dispatched after the specific time. | |||
/// </summary> | |||
/// <param name="eventObject">The event object</param> | |||
/// <param name="dispatchDelay">The delay time before dispatch this event</param> | |||
void Post(object eventObject, TimeSpan dispatchDelay); | |||
/// <summary> | |||
/// Register event handlers in the handler instance. | |||
/// | |||
/// One handler instance may have many event handler methods. | |||
/// These methods have EventSubscriberAttribute contract and exactly one parameter. | |||
/// </summary> | |||
/// <param name="handler">The instance of event handler class</param> | |||
void Register(object handler); | |||
/// <summary> | |||
/// Unregister event handlers belong to the handler instance. | |||
/// | |||
/// One handler instance may have many event handler methods. | |||
/// These methods have EventSubscriberAttribute contract and exactly one parameter. | |||
/// </summary> | |||
/// <param name="handler">The instance of event handler class</param> | |||
void Unregister(object handler); | |||
} | |||
} |
@@ -1,9 +0,0 @@ | |||
namespace Cap.Consistency.EventBus | |||
{ | |||
public interface IEventBusFactory | |||
{ | |||
IEventBus CreateEventBus<TEventBus>() where TEventBus : IEventBus; | |||
IEventBus CreateEventBus<TEventBus>(long maxPendingEventNumber) where TEventBus : IEventBus; | |||
} | |||
} |
@@ -1,119 +0,0 @@ | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Reflection; | |||
using System.Threading.Tasks; | |||
using Microsoft.Extensions.Logging; | |||
using Microsoft.Extensions.Options; | |||
namespace Cap.Consistency.EventBus | |||
{ | |||
/// <summary> | |||
/// The OrderedEventBus class is a simple and fast IEventBus implementation which processes event in the delivery order. | |||
/// </summary> | |||
/// <remarks>If you do not need the event processed in the delivery order, use SimpleEventBus instead.</remarks> | |||
public class OrderedEventBus | |||
: EventBusBase, IEventBus | |||
{ | |||
private readonly BlockingCollection<object> _eventQueue; | |||
/// <summary> | |||
/// The pending event number which does not yet dispatched. | |||
/// </summary> | |||
public override long PendingEventNumber { | |||
get { | |||
return Math.Max(_eventQueue.Count, 0); | |||
} | |||
} | |||
public override bool IsDispatcherEnabled { | |||
get { | |||
return true; | |||
} | |||
} | |||
public OrderedEventBus(ILoggerFactory loggerFactory, IOptions<EventBusOptions> options) | |||
: this(loggerFactory, options?.Value.MaxPendingEventNumber32 ?? 0) { | |||
} | |||
/// <summary> | |||
/// The constructor of OrderedEventBus. | |||
/// </summary> | |||
/// <param name="maxPendingEventNumber">The maximum pending event number which does not yet dispatched</param> | |||
public OrderedEventBus(ILoggerFactory loggerFactory, int maxPendingEventNumber, bool shouldStart = true) | |||
: base(loggerFactory) { | |||
this._eventQueue = new BlockingCollection<object>( | |||
maxPendingEventNumber > 0 | |||
? maxPendingEventNumber | |||
: DefaultMaxPendingEventNumber); | |||
if (shouldStart) { | |||
this.Start(); | |||
} | |||
} | |||
/// <summary> | |||
/// Post an event to the event bus, dispatched after the specific time. | |||
/// </summary> | |||
/// <remarks>If you do not need the event processed in the delivery order, use SimpleEventBus instead.</remarks> | |||
/// <param name="eventObject">The event object</param> | |||
/// <param name="dispatchDelay">The delay time before dispatch this event</param> | |||
public override void Post(object eventObject, TimeSpan dispatchDelay) { | |||
int dispatchDelayMs = (int)dispatchDelay.TotalMilliseconds; | |||
if (dispatchDelayMs >= 1) { | |||
Task.Delay(dispatchDelayMs).ContinueWith(task => _eventQueue.Add(eventObject)); | |||
} | |||
else { | |||
_eventQueue.Add(eventObject); | |||
} | |||
} | |||
protected override async Task<bool> Process() { | |||
object eventObject = null; | |||
try { | |||
eventObject = _eventQueue.Take(); | |||
InvokeEventHandler(eventObject); | |||
} | |||
catch (Exception de) { | |||
if (de is ObjectDisposedException) { | |||
return await Task.FromResult(true); | |||
} | |||
this._logger.LogError("Dispatch event ({0}) failed: {1}{2}{3}", eventObject, de.Message, Environment.NewLine, de.StackTrace); | |||
} | |||
return await Task.FromResult(true); | |||
} | |||
protected void InvokeEventHandler(object eventObject, Action<bool, Exception, object, Type> resultCallback = null) { | |||
List<Task> taskList = null; | |||
// ReSharper disable once ForCanBeConvertedToForeach | |||
for (int i = 0; i < _eventHandlerList.Count; i++) { | |||
// ReSharper disable once InconsistentlySynchronizedField | |||
EventHandlerHolder record = _eventHandlerList[i]; | |||
if (eventObject == null || record.ParameterType.IsInstanceOfType(eventObject)) { | |||
Task task = Task.Run(() => { | |||
this.OnMessageReceieved(record); | |||
var isVoid = record.MethodInfo.ReturnType == typeof(void); | |||
try { | |||
var result = record.MethodInfo.Invoke(record.Handler, new[] { eventObject }); | |||
resultCallback?.Invoke(isVoid, null, result, record.MethodInfo.ReturnType); | |||
} | |||
catch (Exception ex) { | |||
this._logger.LogError(ex.Message + Environment.NewLine + ex.StackTrace); | |||
resultCallback?.Invoke(isVoid, ex, null, record.MethodInfo.ReturnType); | |||
} | |||
}); | |||
if (taskList == null) taskList = new List<Task>(); | |||
taskList.Add(task); | |||
//record.MethodInfo.Invoke(record.Handler, new[] { eventObject }); | |||
} | |||
} | |||
if (taskList != null) { | |||
Task.WaitAll(taskList.ToArray()); | |||
} | |||
else { | |||
resultCallback?.Invoke(false, null, null, null); | |||
} | |||
} | |||
} | |||
} |
@@ -1,25 +0,0 @@ | |||
using System; | |||
namespace Cap.Consistency.EventBus | |||
{ | |||
public class ReceiveResult | |||
{ | |||
public bool IsSucceeded { get; set; } | |||
public bool IsVoid { get; set; } | |||
public object Result { get; set; } | |||
public Type ResultType { get; set; } | |||
public Exception Exception { get; set; } | |||
public ReceiveResult(bool isSucceeded, bool isVoid, object result, Exception ex = null, Type resultType = null) { | |||
this.IsSucceeded = isSucceeded; | |||
this.IsVoid = isVoid; | |||
this.Result = result; | |||
this.Exception = ex; | |||
this.ResultType = (resultType ?? result?.GetType()) ?? typeof(object); | |||
} | |||
} | |||
} |
@@ -1,159 +0,0 @@ | |||
//#define UseTotalEventNumber | |||
using System; | |||
using System.Reflection; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using Microsoft.Extensions.Logging; | |||
using Microsoft.Extensions.Options; | |||
namespace Cap.Consistency.EventBus | |||
{ | |||
/// <summary> | |||
/// The SimpleEventBus class is a simple and fast IEventBus implementation. | |||
/// </summary> | |||
/// <remarks> | |||
/// <para>The event may be processed out of the delivery order under heavy load.</para> | |||
/// <para>If you need the event processed in the delivery order, use OrderedEventBus instead.</para> | |||
/// </remarks> | |||
public class SimpleEventBus | |||
: EventBusBase, IEventBus | |||
{ | |||
private readonly long _maxPendingEventNumber; | |||
// Interlocked operation cause the performance drop at least 10% !. | |||
private long _pendingEventNumber; | |||
private bool _isDispatcherEnabled; | |||
#if UseTotalEventNumber | |||
// This counter cause the performance drop at least 5% ! | |||
private long _totalEventNumber; | |||
// The total event number which post to the event bus. | |||
// This counter cause the performance drop at least 5% ! | |||
public long TotalEventNumber | |||
{ | |||
get | |||
{ | |||
return Interlocked.Read(ref _totalEventNumber); | |||
} | |||
} | |||
#endif | |||
/// <summary> | |||
/// The pending event number which does not yet dispatched. | |||
/// </summary> | |||
public override long PendingEventNumber { | |||
get { | |||
return Math.Max(Interlocked.Read(ref _pendingEventNumber), 0); | |||
} | |||
} | |||
public override bool IsDispatcherEnabled { | |||
get { | |||
return this._isDispatcherEnabled; | |||
} | |||
} | |||
public SimpleEventBus(ILoggerFactory loggerFactory, IOptions<EventBusOptions> options) | |||
: this(loggerFactory, options?.Value.MaxPendingEventNumber ?? 0) { | |||
} | |||
/// <summary> | |||
/// The constructor of SimpleEventBus. | |||
/// </summary> | |||
/// <param name="maxPendingEventNumber">The maximum pending event number which does not yet dispatched</param> | |||
public SimpleEventBus(ILoggerFactory loggerFactory, long maxPendingEventNumber, bool shouldStart = true) | |||
: base(loggerFactory) { | |||
this._maxPendingEventNumber = maxPendingEventNumber > 0 ? maxPendingEventNumber : DefaultMaxPendingEventNumber; | |||
this._isDispatcherEnabled = false; | |||
if (shouldStart) { | |||
this.Start(); | |||
} | |||
} | |||
public override void Start() { | |||
if (this.IsRunning) { | |||
return; | |||
} | |||
this._isDispatcherEnabled = true; | |||
} | |||
public override void Stop(int timeout = 2000) { | |||
this._isDispatcherEnabled = false; | |||
} | |||
/// <summary> | |||
/// Post an event to the event bus, dispatched after the specific time. | |||
/// </summary> | |||
/// <remarks> | |||
/// <para>The event may be processed out of the delivery order under heavy load.</para> | |||
/// <para>If you need the event processed in the delivery order, use OrderedEventBus instead.</para> | |||
/// </remarks> | |||
/// <param name="eventObject">The event object</param> | |||
/// <param name="dispatchDelay">The delay time before dispatch this event</param> | |||
public override void Post(object eventObject, TimeSpan dispatchDelay) { | |||
if (!this._isDispatcherEnabled) return; | |||
int dispatchDelayMs = (int)dispatchDelay.TotalMilliseconds; | |||
while (Interlocked.Read(ref _pendingEventNumber) >= _maxPendingEventNumber) { | |||
this._logger.LogWarning("Too many events in the EventBus, pendingEventNumber={0}, maxPendingEventNumber={1}{2}PendingEvent='{3}', dispatchDelay={4}ms", | |||
PendingEventNumber, _maxPendingEventNumber, Environment.NewLine, eventObject, dispatchDelayMs); | |||
Task.Delay(16).Wait(); | |||
} | |||
if (dispatchDelayMs >= 1) { | |||
Task.Delay(dispatchDelayMs).ContinueWith(task => { | |||
DispatchMessage(eventObject); | |||
}); | |||
} | |||
else { | |||
Task.Run(() => DispatchMessage(eventObject)); | |||
} | |||
Interlocked.Increment(ref _pendingEventNumber); | |||
// Interlocked.Increment(ref _totalEventNumber); | |||
} | |||
protected override Task ThreadWorker(object userObject) { | |||
throw new NotSupportedException(); | |||
} | |||
protected override Task<bool> Process() { | |||
throw new NotSupportedException(); | |||
} | |||
protected void DispatchMessage(object eventObject) { | |||
try { | |||
// ReSharper disable once ForCanBeConvertedToForeach | |||
for (int i = 0; i < _eventHandlerList.Count; i++) { | |||
// ReSharper disable once InconsistentlySynchronizedField | |||
EventHandlerHolder record = _eventHandlerList[i]; | |||
if (eventObject == null || record.ParameterType.IsInstanceOfType(eventObject)) { | |||
Task.Run(() => { | |||
try { | |||
this.OnMessageReceieved(record); | |||
record.MethodInfo.Invoke(record.Handler, new[] { eventObject }); | |||
} | |||
catch (Exception ie) { | |||
this._logger.LogWarning("Event handler (class '{0}@{1}', method '{2}') failed: {3}{4}{5}{4}eventObject: {6}", | |||
record.Handler.GetType(), record.Handler.GetHashCode(), record.MethodInfo, | |||
ie.Message, Environment.NewLine, ie.StackTrace, eventObject); | |||
} | |||
}); | |||
} | |||
} | |||
} | |||
catch (Exception de) { | |||
this._logger.LogError("Dispatch event ({0}) failed: {1}{2}{3}", | |||
eventObject, de.Message, Environment.NewLine, de.StackTrace); | |||
} | |||
finally { | |||
Interlocked.Decrement(ref _pendingEventNumber); | |||
} | |||
} | |||
} | |||
} |