diff --git a/src/Stateless/StateMachine.Async.cs b/src/Stateless/StateMachine.Async.cs index b319813a..169138de 100644 --- a/src/Stateless/StateMachine.Async.cs +++ b/src/Stateless/StateMachine.Async.cs @@ -135,28 +135,37 @@ async Task InternalFireAsync(TTrigger trigger, params object[] args) /// A variable-length parameters list containing arguments. async Task InternalFireQueuedAsync(TTrigger trigger, params object[] args) { - if (_firing) + if (!_firing) { - _eventQueue.Enqueue(new QueuedTrigger { Trigger = trigger, Args = args }); - return; - } - - try - { - _firing = true; - - await InternalFireOneAsync(trigger, args).ConfigureAwait(RetainSynchronizationContext); + await _lock.WaitAsync(); - while (_eventQueue.Count != 0) + if(!_firing) { - var queuedEvent = _eventQueue.Dequeue(); - await InternalFireOneAsync(queuedEvent.Trigger, queuedEvent.Args).ConfigureAwait(RetainSynchronizationContext); + try + { + _firing = true; + _lock.Release(); + await InternalFireOneAsync(trigger, args).ConfigureAwait(RetainSynchronizationContext); + + while (!_eventQueue.IsEmpty) + { + bool suuccess = _eventQueue.TryDequeue(out QueuedTrigger queuedEvent); + if (suuccess) + { + await InternalFireOneAsync(queuedEvent.Trigger, queuedEvent.Args).ConfigureAwait(RetainSynchronizationContext); + } + } + return; + } + finally + { + _firing = false; + } } + _lock.Release(); } - finally - { - _firing = false; - } + + _eventQueue.Enqueue(new QueuedTrigger { Trigger = trigger, Args = args }); } async Task InternalFireOneAsync(TTrigger trigger, params object[] args) diff --git a/src/Stateless/StateMachine.cs b/src/Stateless/StateMachine.cs index 270cc620..abe5f74d 100644 --- a/src/Stateless/StateMachine.cs +++ b/src/Stateless/StateMachine.cs @@ -1,7 +1,9 @@ using Stateless.Reflection; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Threading; namespace Stateless { @@ -32,6 +34,7 @@ public partial class StateMachine private readonly OnTransitionedEvent _onTransitionCompletedEvent; private readonly TState _initialState; private readonly FiringMode _firingMode; + private readonly SemaphoreSlim _lock; private class QueuedTrigger { @@ -39,7 +42,7 @@ private class QueuedTrigger public object[] Args { get; set; } } - private readonly Queue _eventQueue = new Queue(); + private readonly ConcurrentQueue _eventQueue = new ConcurrentQueue(); private bool _firing; /// @@ -102,6 +105,7 @@ public StateMachine(TState initialState, FiringMode firingMode) : this() _unhandledTriggerAction = new UnhandledTriggerAction.Sync(DefaultUnhandledTriggerAction); _onTransitionedEvent = new OnTransitionedEvent(); _onTransitionCompletedEvent = new OnTransitionedEvent(); + _lock = new SemaphoreSlim(1, 1); } /// @@ -374,8 +378,12 @@ private void InternalFireQueued(TTrigger trigger, params object[] args) // Empty queue for triggers while (_eventQueue.Any()) { - var queuedEvent = _eventQueue.Dequeue(); - InternalFireOne(queuedEvent.Trigger, queuedEvent.Args); + bool suuccess = _eventQueue.TryDequeue(out QueuedTrigger queuedEvent); + if(suuccess) + { + InternalFireOne(queuedEvent.Trigger, queuedEvent.Args); + } + } } finally diff --git a/test/Stateless.Tests/InitialTransitionFixture.cs b/test/Stateless.Tests/InitialTransitionFixture.cs index 22cfa4e8..abf671a2 100644 --- a/test/Stateless.Tests/InitialTransitionFixture.cs +++ b/test/Stateless.Tests/InitialTransitionFixture.cs @@ -335,5 +335,55 @@ public async void AsyncTransitionEvents_OrderingWithInitialTransition() Assert.Equal(expectedOrdering[i], actualOrdering[i]); } } + + [Fact] + public async Task CheckMultipleFireAsync() + { + for (int i = 0; i < 200; i++) + { + await CheckAsyncMethod(); + } + } + + private async Task CheckAsyncMethod() + { + // Create a state machine + var sm = new StateMachine(State.A); + + sm.Configure(State.A).Permit(Trigger.X, State.B); + sm.Configure(State.A).Permit(Trigger.Y, State.C); + sm.Configure(State.B).Permit(Trigger.Y, State.D); + sm.Configure(State.B).Permit(Trigger.X, State.A); + sm.Configure(State.C).Permit(Trigger.Y, State.D); + sm.Configure(State.C).Permit(Trigger.X, State.A); + sm.Configure(State.D).Permit(Trigger.Y, State.B); + sm.Configure(State.D).Permit(Trigger.X, State.C); + + + // Create some tasks + List tasks = new List(); + + for (int i = 0; i < 20; i++) + { + Task taskTriggerX = Task.Run(() => FireTrigger(sm, Trigger.X, false)); + Task taskTriggerY = Task.Run(() => FireTrigger(sm, Trigger.Y, false)); + Task taskTriggerDelay = Task.Run(() => FireTrigger(sm, Trigger.X, true)); + tasks.Add(taskTriggerX); + tasks.Add(taskTriggerY); + tasks.Add(taskTriggerDelay); + } + + // Wait for tasks to complete + await Task.WhenAll(tasks); + } + + private async Task FireTrigger(StateMachine sm, Trigger trigger, bool addDelay) + { + if(addDelay) + { + await Task.Delay(20); + } + await sm.FireAsync(trigger); + } } }