From 2ab545b3eca3af1533ef19460ecf38503b91176e Mon Sep 17 00:00:00 2001 From: Mike Clift Date: Fri, 2 Jun 2023 17:59:35 +0100 Subject: [PATCH 1/2] bugfix: Execute OnEntryFromAsync actions asynchronously --- src/Stateless/EntryActionBehaviour.cs | 9 ++++-- test/Stateless.Tests/AsyncActionsFixture.cs | 36 +++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/Stateless/EntryActionBehaviour.cs b/src/Stateless/EntryActionBehaviour.cs index 280452ef..5fb38275 100644 --- a/src/Stateless/EntryActionBehaviour.cs +++ b/src/Stateless/EntryActionBehaviour.cs @@ -95,13 +95,16 @@ public AsyncFrom(TTriggerType trigger, Func action, public override void Execute(Transition transition, object[] args) { - if (transition.Trigger.Equals(Trigger)) - base.Execute(transition, args); + ExecuteAsync(transition, args); } public override Task ExecuteAsync(Transition transition, object[] args) { - Execute(transition, args); + if (transition.Trigger.Equals(Trigger)) + { + return base.ExecuteAsync(transition, args); + } + return TaskResult.Done; } } diff --git a/test/Stateless.Tests/AsyncActionsFixture.cs b/test/Stateless.Tests/AsyncActionsFixture.cs index 4e9b63ba..f44eac22 100644 --- a/test/Stateless.Tests/AsyncActionsFixture.cs +++ b/test/Stateless.Tests/AsyncActionsFixture.cs @@ -472,6 +472,42 @@ public void VerifyNotEnterSuperstateWhenDoingInitialTransition() Assert.Equal(State.D, sm.State); } + + [Fact] + public async Task OnEntryFromAsync_WhenTriggered_InvokesAction() + { + bool wasInvoked = false; + + var sm = new StateMachine(State.A); + + sm.Configure(State.A).Permit(Trigger.X, State.B); + + sm.Configure(State.B) + .OnEntryFromAsync(Trigger.X, async () => await Task.Run(() => { wasInvoked = true; })); + + await sm.FireAsync(Trigger.X); + + Assert.True(wasInvoked); + } + + [Fact] + public async Task OnEntryFromAsync_WhenEnteringByAnotherTrigger_InvokesAction() + { + bool wasInvoked = false; + + var sm = new StateMachine(State.A); + + sm.Configure(State.A) + .Permit(Trigger.X, State.B) + .Permit(Trigger.Y, State.B); ; + + sm.Configure(State.B) + .OnEntryFromAsync(Trigger.X, async () => await Task.Run(() => { wasInvoked = true; })); + + await sm.FireAsync(Trigger.Y); + + Assert.False(wasInvoked); + } } } From 5bcedd687aa3043fd807f254fdd137adc0f52f80 Mon Sep 17 00:00:00 2001 From: Mike Clift Date: Fri, 2 Jun 2023 18:20:29 +0100 Subject: [PATCH 2/2] Throw InvalidOperationException when firing async action synchronously. --- src/Stateless/EntryActionBehaviour.cs | 5 ++- test/Stateless.Tests/AsyncActionsFixture.cs | 34 ++++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/Stateless/EntryActionBehaviour.cs b/src/Stateless/EntryActionBehaviour.cs index 5fb38275..8023cebc 100644 --- a/src/Stateless/EntryActionBehaviour.cs +++ b/src/Stateless/EntryActionBehaviour.cs @@ -95,7 +95,10 @@ public AsyncFrom(TTriggerType trigger, Func action, public override void Execute(Transition transition, object[] args) { - ExecuteAsync(transition, args); + if (transition.Trigger.Equals(Trigger)) + { + base.Execute(transition, args); + } } public override Task ExecuteAsync(Transition transition, object[] args) diff --git a/test/Stateless.Tests/AsyncActionsFixture.cs b/test/Stateless.Tests/AsyncActionsFixture.cs index f44eac22..e8dd5dba 100644 --- a/test/Stateless.Tests/AsyncActionsFixture.cs +++ b/test/Stateless.Tests/AsyncActionsFixture.cs @@ -473,6 +473,19 @@ public void VerifyNotEnterSuperstateWhenDoingInitialTransition() Assert.Equal(State.D, sm.State); } + [Fact] + public void OnEntryFromAsync_WhenTriggeredSynchronously_Throws() + { + var sm = new StateMachine(State.A); + + sm.Configure(State.A).Permit(Trigger.X, State.B); + + sm.Configure(State.B) + .OnEntryFromAsync(Trigger.X, async () => await Task.Run(() => { })); + + Assert.Throws(() => sm.Fire(Trigger.X)); + } + [Fact] public async Task OnEntryFromAsync_WhenTriggered_InvokesAction() { @@ -490,6 +503,25 @@ public async Task OnEntryFromAsync_WhenTriggered_InvokesAction() Assert.True(wasInvoked); } + [Fact] + public void OnEntryFromAsync_WhenEnteringByAnotherTriggerSynchronously_DoesNotThrow() + { + bool wasInvoked = false; + + var sm = new StateMachine(State.A); + + sm.Configure(State.A) + .Permit(Trigger.X, State.B) + .Permit(Trigger.Y, State.B); + + sm.Configure(State.B) + .OnEntryFromAsync(Trigger.X, async () => await Task.Run(() => { wasInvoked = true; })); + + sm.Fire(Trigger.Y); + + Assert.False(wasInvoked); + } + [Fact] public async Task OnEntryFromAsync_WhenEnteringByAnotherTrigger_InvokesAction() { @@ -499,7 +531,7 @@ public async Task OnEntryFromAsync_WhenEnteringByAnotherTrigger_InvokesAction() sm.Configure(State.A) .Permit(Trigger.X, State.B) - .Permit(Trigger.Y, State.B); ; + .Permit(Trigger.Y, State.B); sm.Configure(State.B) .OnEntryFromAsync(Trigger.X, async () => await Task.Run(() => { wasInvoked = true; }));