Closed zorgoz closed 6 years ago
On this page it is stated:
Extensions can change the behaviour of the state machine in...
- Firing events on the state machine as a reaction to a call on the extension (e.g. perform transition to the error state on an exception)
I tried to folloow it in this attempt:
public enum States { S1, S2, E }
public enum Actions { A1, E }
async Task Main()
{
var fsm = new AsyncPassiveStateMachine<States, Actions>();
fsm.In(States.S1)
.On(Actions.A1)
.Goto(States.S2)
.Execute(() => throw new Exception());
fsm.In(States.S1)
.ExecuteOnEntry(() => "Entry S1".Dump())
.On(Actions.E).Goto(States.E)
;
fsm.In(States.S2)
.ExecuteOnEntry(() => "Entry S2".Dump())
.On(Actions.E).Goto(States.E)
;
fsm.In(States.E)
.ExecuteOnEntry(() => "Entry E".Dump())
;
fsm.TransitionExceptionThrown += (sender, args) => args.Dump();
fsm.AddExtension(new Redirector<States, Actions>(fsm));
fsm.Initialize(States.S1);
await fsm.Fire(Actions.A1);
await fsm.Start();
}
public class Redirector<TState, TEvent> : AsyncExtensionBase<TState, TEvent>
where TState : IComparable
where TEvent : IComparable
{
AsyncPassiveStateMachine<States, Actions> machine;
public Redirector(AsyncPassiveStateMachine<States, Actions> machine)
{
this.machine = machine;
}
public override void HandlingTransitionException(IStateMachineInformation<TState, TEvent> stateMachine, ITransition<TState, TEvent> transition, ITransitionContext<TState, TEvent> context, ref Exception exception)
{
machine.FirePriority(Actions.E);
}
}
Unfortunatelly the machine still enters S2 and the transition to E is performed ftom there, but only if it is defined. As I can't forcefully move the machine into a state, and I can't call Initialize
either, the ITransition
has no Abort
method or something equivalent, thus no actual error state and error transition can be defined.
This is the simplest workaround I could figure out. But it performs a self-transition instread of an internal one, and it is really painfull to implement for a large machine:
public enum States { S1, S2 }
public enum Actions { A1, Back }
async Task Main()
{
var fsm = new AsyncPassiveStateMachine<States, Actions>();
bool withException = false;
fsm.In(States.S1)
.On(Actions.A1)
.Goto(States.S2)
.Execute(() => throw new Exception());
fsm.In(States.S1)
.ExecuteOnEntry(() => "Entry S1".Dump())
;
fsm.In(States.S2)
.ExecuteOnEntry(() =>
{
if (withException)
{
"Diversion...".Dump();
fsm.FirePriority(Actions.Back);
return;
}
"Entry S2".Dump();
})
.On(Actions.Back).Goto(States.S1)
;
fsm.TransitionExceptionThrown += (sender, args) => withException = true;
fsm.TransitionExceptionThrown += (sender, args) => args.Dump();
fsm.Initialize(States.S1);
await fsm.Fire(Actions.A1);
await fsm.Start();
}
It was a design decision that the state machine will end up in the target state of the transition even in the case of an exception. The reasoning is that the state machine was told to execute the transition and therefore cannot stay in the state it was.
Is it possible that you can handle the exception during the transition? So that in no case the exception is thrown at the state machine?
Otherwise, I see no easy way to achieve what you are looking for.
Or can you redesign the states like this:
(Waiting for something to write to the DB) --write--> (writing to the DB) --success--> (Done)
--failed--> (Error state)
Thank you for your answer. Actually, as the machine is not independent of its environment, reacting to the exception with a specific transition would be the theoretical handling of the exception. The only workaround I could come up with is to split the transition in two:
S1*A1-(T)->S2A
S2A*null-()->S2B,
S2A*error-()->S1
but as null transitions are also absent, it would make the machine far more complex.
Thank you for your suggestions, but to be able to produce maintainable code I have created my own state machine supporting both diversions on an exception and null transitions.
Hello,
I have to handle exceptions in transition somehow. Either go to another state or stay in the current state, but I can't deal with the actual behavior of the machine is such situation. See following code:
If there is no exception listener registered, the machine stops at exception. With one registered, it detects the exception but the transition is still performed, bringing the machine in an inconsistent state. Guards are of no use here.
Imagine following situation: a database operation is performed in the transition. But the connection fails meanwhile. I need to be noticed about it, but in no case should I step forward. How can I properly handle this in the machine?
Please advice!