diff --git a/src/WeakEvent.Tests/WeakEventLockingTests.cs b/src/WeakEvent.Tests/WeakEventLockingTests.cs new file mode 100644 index 0000000..38b43d2 --- /dev/null +++ b/src/WeakEvent.Tests/WeakEventLockingTests.cs @@ -0,0 +1,22 @@ +using Xunit.Abstractions; + +namespace ByteAether.WeakEvent.Tests; +public class WeakEventLockingTests(ITestOutputHelper output) +{ + private readonly ITestOutputHelper _output = output; + + [Fact] + public async Task Handler_CanUnsubscribeItself() + { + WeakEvent e = new(); + Action? handler = null; + handler = () => + { + _output.WriteLine("Handler executing..."); + e.Unsubscribe(handler!); + }; + + e.Subscribe(handler); + await e.PublishAsync(); + } +} \ No newline at end of file diff --git a/src/WeakEvent/WeakEvent.cs b/src/WeakEvent/WeakEvent.cs index 2c70254..979e40e 100644 --- a/src/WeakEvent/WeakEvent.cs +++ b/src/WeakEvent/WeakEvent.cs @@ -153,7 +153,21 @@ public abstract class WeakEventBase /// /// Number of alive subscribers currently registered to the event. /// - public int SubscriberCount => _handlers.Count(x => x.IsAlive); + public int SubscriberCount + { + get + { + _lock.Wait(); + try + { + return _handlers.Count(x => x.IsAlive); + } + finally + { + _lock.Release(); + } + } + } /// /// Subscribes the specified delegate handler to the event. @@ -170,7 +184,15 @@ protected void Subscribe(Delegate handler) throw new ArgumentNullException(nameof(handler)); } - _handlers.Add(new WeakEventHandler(handler)); + _lock.Wait(); + try + { + _handlers.Add(new WeakEventHandler(handler)); + } + finally + { + _lock.Release(); + } } /// @@ -185,7 +207,15 @@ protected bool Unsubscribe(Delegate handler) throw new ArgumentNullException(nameof(handler)); } - return _handlers.RemoveAll(weh => weh.Matches(handler)) > 0; + _lock.Wait(); + try + { + return _handlers.RemoveAll(weh => weh.Matches(handler)) > 0; + } + finally + { + _lock.Release(); + } } /// @@ -196,20 +226,22 @@ protected bool Unsubscribe(Delegate handler) /// The cancellation token to cancel the operation. protected async Task PublishAsync(List args, CancellationToken cancellationToken = default) { + List handlers; await _lock.WaitAsync(cancellationToken); try { _handlers.RemoveAll(x => !x.IsAlive); - - foreach (var handler in _handlers) - { - await handler.InvokeAsync(args, cancellationToken); - } + handlers = [.._handlers]; } finally { _lock.Release(); } + + foreach (var handler in handlers) + { + await handler.InvokeAsync(args, cancellationToken); + } } } \ No newline at end of file