Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/312.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 确保给定类型的单个未完成任务的模式_C#_.net_Task Parallel Library_Design Patterns - Fatal编程技术网

C# 确保给定类型的单个未完成任务的模式

C# 确保给定类型的单个未完成任务的模式,c#,.net,task-parallel-library,design-patterns,C#,.net,Task Parallel Library,Design Patterns,考虑以下几点: public class SomeService { public Task StartAsync() { return Task.Factory .StartNew(() => DoStartup()); } public Task StopAsync() { return Task.Factory .StartNew(() => DoShutd

考虑以下几点:

public class SomeService
{
    public Task StartAsync()
    {
        return Task.Factory
            .StartNew(() => DoStartup());
    }

    public Task StopAsync()
    {
        return Task.Factory
            .StartNew(() => DoShutdown());
    }
}
上面的问题是,如果对
StartAsync
stopsync
进行了多次调用,则将创建多个任务来启动/停止服务。实际上,一个人在任何时候都只希望一个启动/停止任务处于活动状态。为此,我通常会这样做:

public class SomeService
{
    private readonly object startSync = new object();
    private readonly object stopSync = new object();
    private Task startTask;
    private Task stopTask;

    public Task StartAsync()
    {
        var startTaskLocal = this.startTask;

        if (startTaskLocal != null)
        {
            return startTaskLocal;
        }

        lock (this.startSync)
        {
            if (this.startTask != null)
            {
                return this.startTask;
            }

            this.startTask = Task.Factory
                .StartNew(() => DoStartup())
                .Then(x =>
                    {
                        lock (this.stopSync)
                        {
                            this.stopTask = null);
                        }
                    });

            return this.startTask;
        }
    }

    public Task StopAsync()
    {
        // similar pattern to above
    }
}
现在,虽然这起作用,但有点难看。在我试图将这个模式封装成一些小的和可重用的东西之前,我想知道是否有一种我不知道的已经建立的方法

另外,我通过让
StartAsync
等待任何未完成的
StopAsync
调用来进一步扩展这个想法,反之亦然。这意味着一次只能进行一次启动或停止操作。通过任务组合,这很容易做到。如果我能首先找出是否有一种现成的方法可以巧妙地实现上述目标,那么我就能找出它是否符合这种扩展行为。

您可以使用。当代码进入AsyncStart时,将状态设置为
Starting
。我建议使用一个回调函数,将对象的状态设置为
Running

停止
停止
提供相同的设置

因此,在您的服务内部,您可能有一个
ServiceState
对象。在调用
AsyncStart
/
AsyncStop
之前,请检查当前状态

更新

... 另一种方法可能是存储和检查数据。关于如何使用
CancellationToken
,有很多示例。这只是我的想法,并不是确认这一定是最好的方式。

可能不是你想要的,但是如果排队而不是忽略是目的(或者是可以接受的),那么一个相对简单的选择就是使用这两个新开始的呼叫


要获得您想要的效果,它需要更多的代码,但可以让您利用已经编写的内容,而不是编写自己的代码。:)

>,如果您希望每次只激活一个类的实例,则应该考虑使用单模式。

我不是模式方面的专家,所以如果我使用的解决方案总是有一个名称,我不知道它。我不使用任务,而是使用线程。这是我的密码

公共类服务
{
私人物品;
私奔;
私有线程任务;
已启动专用自动存储事件任务;
public bool正在运行{get{返回this.running;}}
公共服务()
{
oLock=新对象();
taskStarted=新的自动重置事件(false);
运行=错误;
}
公开作废开始()
{
//如果我们无法获得锁,则有启动或停止操作
//正在进行中,所以最好离开。如果已经运行,也要离开。
如果(正在运行| |!监视器.TryEnter(oLock))
返回;
//设置运行标志以防止其他线程再次启动服务
运行=真;
//创建并启动线程
threadTask=新线程(新线程开始(任务));
threadTask.IsBackground=true;
threadTask.Start();
//等待任务执行开始。这是可选的
taskStarted.WaitOne();
//松开锁
Pulsell监护仪(奥洛克);
监控出口(oLock);
}
公共停车场()
{
//如果我们无法获得锁,则有启动或停止操作
//正在进行中,所以最好离开。如果不运行,也要离开。
如果(!running | |!Monitor.TryEnter(oLock))
返回;
//清除正在运行的任务以防止重新进入
运行=错误;
//在这里我们可以中止线程。这是一个可选的依赖于任务
threadTask.Abort();
//等到鞭打结束
threadTask.Join();
//清楚的
threadTask=null;
//松开锁
Pulsell监护仪(奥洛克);
监控出口(oLock);
}
受保护的虚拟无效任务()
{
//设置任务并分配资源
taskStarted.Set();
//执行任务轮询
(跑步时)
{
}
//完成任务并释放资源
}
}
其中一个技巧是使用Monitor类而不是lock语句,因此尝试启动服务的第一个线程将获胜,而其他线程将赢得已经启动或停止的服务。如果你打算颠倒顺序,一定要小心

if(Monitor.TryEnter()| | running)
返回;
您必须呼叫监视器。退出,否则您的应用程序将死锁

当然,您可以更改表示空闲、启动、运行和停止状态的枚举的运行布尔值

另外,最好将类抽象化,让继承者重写要在/而不是while语句中调用的方法

希望这有帮助。
致以最诚挚的问候。

我终于在我的代码库中找到了解决这个问题的方法,我想在这里分享我的解决方案。快速解释:我有一个
StateMachineTaskFactory
类,其中
T
定义有效状态(通常是枚举)。此任务工厂允许您注册有效的转换(例如,当转换到
Started
状态时,在转换过程中使用
Starting
)并执行转换。它保证了状态机语义,同时维护了异步API。它基本上形式化了我的原始代码中的状态机,以健壮和可重用的方式进行

首先,这里有一个在我的问题中介绍的用例中如何使用它的示例:

public enum ServiceState
{
    Uninitialized,
    Initializing,
    Initialized,
    Starting,
    Started,
    Stopping,
    Stopped
}

public class SomeService
{
    private readonly StateMachineTaskFactory<ServiceState> stateMachineTaskFactory;

    public Service()
    {
        this.stateMachineTaskFactory = new StateMachineTaskFactory<ServiceState>();
        this.stateMachineTaskFactory.RegisterTransition(ServiceState.Initializing, ServiceState.Initialized, this.OnInitializeAsync);
        this.stateMachineTaskFactory.RegisterTransition(ServiceState.Starting, ServiceState.Started, this.OnStartAsync);
        this.stateMachineTaskFactory.RegisterTransition(ServiceState.Stopping, ServiceState.Stopped, this.OnStopAsync);
    }

    // we don't support cancellation in our initialize API
    public Task InitializeAsync()
    {
        return this.stateMachineTaskFactory.TransitionTo(ServiceState.Initialized);
    }

    public Task StartAsync(CancellationToken cancellationToken = default(CancellationToken))
    {
        return this.stateMachineTaskFactory.TransitionTo(ServiceState.Started, cancellationToken);
    }

    public Task StopAsync(CancellationToken cancellationToken = default(CancellationToken))
    {
        return this.stateMachineTaskFactory.TransitionTo(ServiceState.Stopped, cancellationToken);
    }

    // even though we don't support cancellation during initialization, we'll still get a cancellation token, but it will CancellationToken.None
    private Task OnInitializeAsync(CancellationToken cancellationToken, object state)
    {
        // return a Task that performs the actual work involved in initializing
    }

    private Task OnStartAsync(CancellationToken cancellationToken, object state)
    {
        // return a Task that performs the actual work involved in starting, passing on the cancellation token as relevant
    }

    private Task OnStopAsync(CancellationToken cancellationToken, object state)
    {
        // return a Task that performs the actual work involved in stopping, passing on the cancellation token as relevant
    }
}
公共枚举服务状态
{
未初始化,
初始化,
初始化,
启动,
起动,
停止,
停止
}
公共课
[Serializable]
public sealed class StateTransitionForbiddenException<T> : InvalidOperationException
    where T : struct
{
    private readonly T targetState;
    private readonly T state;

    public StateTransitionForbiddenException()
    {
    }

    public StateTransitionForbiddenException(string message)
        : base(message)
    {
    }

    public StateTransitionForbiddenException(string message, Exception innerException)
        : base(message, innerException)
    {
    }

    public StateTransitionForbiddenException(T targetState, T state)
        : base("A transition to state '" + targetState + "' was forbidden by the validate transition callback.")
    {
        this.targetState = targetState;
        this.state = state;
    }

    public StateTransitionForbiddenException(string message, T targetState, T state)
        : base(message)
    {
        this.targetState = targetState;
        this.state = state;
    }

    private StateTransitionForbiddenException(SerializationInfo info, StreamingContext context)
        : base(info, context)
    {
        this.targetState = (T)info.GetValue("TargetState", typeof(T));
        this.state = (T)info.GetValue("State", typeof(T));
    }

    public T TargetState
    {
        get { return this.targetState; }
    }

    public T State
    {
        get { return this.state; }
    }

    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        base.GetObjectData(info, context);

        info.AddValue("TargetState", this.targetState);
        info.AddValue("State", this.state);
    }
}

[DebuggerDisplay("{OldState} -> {NewState}")]
public sealed class StateChangedEventArgs<T> : EventArgs
    where T : struct
{
    private readonly T oldState;
    private readonly T newState;

    public StateChangedEventArgs(T oldState, T newState)
    {
        this.oldState = oldState;
        this.newState = newState;
    }

    public T OldState
    {
        get { return this.oldState; }
    }

    public T NewState
    {
        get { return this.newState; }
    }
}

public delegate Task CreateTaskForTransitionCallback(CancellationToken cancellationToken, object state);

public delegate bool ValidateTransitionCallback<T>(T currentState)
    where T : struct;

public class StateMachineTaskFactory<T> : TaskFactory
    where T : struct
{
    private static readonly ExceptionHelper exceptionHelper = new ExceptionHelper(typeof(StateMachineTaskFactory<>));
    private readonly ConcurrentDictionary<T, TransitionRegistrationInfo> transitionRegistrations;
    private readonly object stateSync;

    // the current state
    private T state;

    // the state to which we're currently transitioning
    private T? transitionToState;

    // the task performing the transition
    private Task transitionToTask;

    public StateMachineTaskFactory()
        : this(default(T))
    {
    }

    public StateMachineTaskFactory(T startState)
    {
        this.transitionRegistrations = new ConcurrentDictionary<T, TransitionRegistrationInfo>();
        this.stateSync = new object();
        this.state = startState;
    }

    public event EventHandler<StateChangedEventArgs<T>> StateChanged;

    public T State
    {
        get
        {
            return this.state;
        }

        private set
        {
            if (!EqualityComparer<T>.Default.Equals(this.state, value))
            {
                var oldState = this.state;
                this.state = value;
                this.OnStateChanged(new StateChangedEventArgs<T>(oldState, value));
            }
        }
    }

    public void RegisterTransition(T beginTransitionState, T endTransitionState, CreateTaskForTransitionCallback createTaskCallback)
    {
        createTaskCallback.AssertNotNull("factory");

        var transitionRegistrationInfo = new TransitionRegistrationInfo(beginTransitionState, createTaskCallback);
        var registered = this.transitionRegistrations.TryAdd(endTransitionState, transitionRegistrationInfo);
        exceptionHelper.ResolveAndThrowIf(!registered, "transitionAlreadyRegistered", endTransitionState);
    }

    public Task TransitionTo(T endTransitionState, CancellationToken cancellationToken = default(CancellationToken), ValidateTransitionCallback<T> validateTransitionCallback = null, object state = null)
    {
        lock (this.stateSync)
        {
            if (EqualityComparer<T>.Default.Equals(this.state, endTransitionState))
            {
                // already in the requested state - nothing to do
                return TaskUtil.FromResult(true);
            }
            else if (this.transitionToState.HasValue && EqualityComparer<T>.Default.Equals(this.transitionToState.Value, endTransitionState))
            {
                // already in the process of transitioning to the requested state - return same transition task
                return this.transitionToTask;
            }
            else if (this.transitionToTask != null)
            {
                // not in the requested state, but there is an outstanding transition in progress, so come back to this request once it's done
                return this.transitionToTask.Then(x => this.TransitionTo(endTransitionState, cancellationToken, validateTransitionCallback, state));
            }
            else if (validateTransitionCallback != null && !validateTransitionCallback(this.State))
            {
                // transition is forbidden, so return a failing task to that affect
                var taskCompletionSource = new TaskCompletionSource<bool>();
                var exception = new StateTransitionForbiddenException<T>(endTransitionState, this.State);
                taskCompletionSource.TrySetException(exception);
                return taskCompletionSource.Task;
            }

            // else, need to transition to the chosen state
            TransitionRegistrationInfo transitionRegistrationInfo;
            var result = this.transitionRegistrations.TryGetValue(endTransitionState, out transitionRegistrationInfo);
            exceptionHelper.ResolveAndThrowIf(!result, "transitionNotRegistered", endTransitionState);

            var beginTransitionState = transitionRegistrationInfo.BeginTransitionState;
            var task = transitionRegistrationInfo.TaskFactory(cancellationToken, state);
            exceptionHelper.ResolveAndThrowIf(task == null, "taskFactoryReturnedNull", endTransitionState);

            var previousState = this.State;
            this.State = beginTransitionState;
            this.transitionToState = endTransitionState;
            this.transitionToTask = task
                .ContinueWith(
                    x =>
                        {
                            if (x.IsFaulted || cancellationToken.IsCancellationRequested)
                            {
                                // faulted or canceled, so roll back to previous state
                                lock (this.stateSync)
                                {
                                    this.State = previousState;
                                    this.transitionToState = null;
                                    this.transitionToTask = null;
                                }

                                if (x.IsFaulted)
                                {
                                    throw x.Exception;
                                }

                                cancellationToken.ThrowIfCancellationRequested();
                            }
                            else
                            {
                                // succeeded, so commit to end state
                                lock (this.stateSync)
                                {
                                    this.State = endTransitionState;
                                    this.transitionToState = null;
                                    this.transitionToTask = null;
                                }
                            }
                        });

            return this.transitionToTask;
        }
    }

    protected virtual void OnStateChanged(StateChangedEventArgs<T> e)
    {
        this.StateChanged.Raise(this, e);
    }

    private struct TransitionRegistrationInfo
    {
        private readonly T beginTransitionState;
        private readonly CreateTaskForTransitionCallback taskFactory;

        public TransitionRegistrationInfo(T beginTransitionState, CreateTaskForTransitionCallback taskFactory)
        {
            this.beginTransitionState = beginTransitionState;
            this.taskFactory = taskFactory;
        }

        public T BeginTransitionState
        {
            get { return this.beginTransitionState; }
        }

        public CreateTaskForTransitionCallback TaskFactory
        {
            get { return this.taskFactory; }
        }
    }
}
public sealed class StateMachineTaskFactoryFixture
{
    #region Supporting Enums

    private enum State
    {
        Undefined,
        Starting,
        Started,
        Stopping,
        Stopped
    }

    #endregion

    [Fact]
    public void default_ctor_uses_default_value_for_start_state()
    {
        var factory = new StateMachineTaskFactory<State>();
        Assert.Equal(State.Undefined, factory.State);
    }

    [Fact]
    public void ctor_can_set_start_state()
    {
        var factory = new StateMachineTaskFactory<State>(State.Stopped);
        Assert.Equal(State.Stopped, factory.State);
    }

    [Fact]
    public void register_transition_throws_if_factory_is_null()
    {
        var factory = new StateMachineTaskFactory<State>();
        Assert.Throws<ArgumentNullException>(() => factory.RegisterTransition(State.Starting, State.Started, null));
    }

    [Fact]
    public void register_transition_throws_if_transition_already_registered()
    {
        var factory = new StateMachineTaskFactory<State>();
        factory.RegisterTransition(State.Starting, State.Started, (ct, o) => TaskUtil.FromResult(true));

        var ex = Assert.Throws<InvalidOperationException>(() => factory.RegisterTransition(State.Starting, State.Started, (ct, o) => TaskUtil.FromResult(true)));
        Assert.Equal("A transition to state 'Started' has already been registered.", ex.Message);
    }

    [Fact]
    public void transition_to_throws_if_no_transition_registered_for_state()
    {
        var factory = new StateMachineTaskFactory<State>();

        var ex = Assert.Throws<InvalidOperationException>(() => factory.TransitionTo(State.Started));
        Assert.Equal("No transition to state 'Started' has been registered.", ex.Message);
    }

    [Fact]
    public void transition_to_throws_if_task_factory_returns_null()
    {
        var factory = new StateMachineTaskFactory<State>();
        factory.RegisterTransition(State.Starting, State.Started, (ct, o) => null);

        var ex = Assert.Throws<InvalidOperationException>(() => factory.TransitionTo(State.Started));
        Assert.Equal("Task factory for end state 'Started' returned null.", ex.Message);
    }

    [Fact]
    public void transition_to_returns_same_task_if_called_multiple_times_whilst_initial_task_is_still_in_progress()
    {
        var factory = new StateMachineTaskFactory<State>();
        factory.RegisterTransition(State.Starting, State.Started, (ct, o) => TaskUtil.Delay(TimeSpan.FromMilliseconds(250)));

        var initialTask = factory.TransitionTo(State.Started);

        Assert.Equal(initialTask, factory.TransitionTo(State.Started));
        Assert.Equal(initialTask, factory.TransitionTo(State.Started));
        Assert.Equal(initialTask, factory.TransitionTo(State.Started));

        Assert.True(initialTask.Wait(TimeSpan.FromSeconds(3)));
    }

    [Fact]
    public void transition_to_returns_completed_task_if_already_in_desired_state()
    {
        var factory = new StateMachineTaskFactory<State>();
        factory.RegisterTransition(State.Starting, State.Started, (ct, o) => TaskUtil.FromResult(true));

        factory.TransitionTo(State.Started).Wait();

        Assert.Equal(TaskStatus.RanToCompletion, factory.TransitionTo(State.Started).Status);
    }

    [Fact]
    public void transition_to_passes_any_state_to_task_creation_function()
    {
        var factory = new StateMachineTaskFactory<State>();
        string receivedState = null;
        factory.RegisterTransition(
            State.Starting,
            State.Started,
            (ct, o) =>
            {
                receivedState = o as string;
                return TaskUtil.FromResult(true);
            });

        factory.TransitionTo(State.Started, CancellationToken.None, null, "here is the state").Wait();

        Assert.Equal("here is the state", receivedState);
    }

    [Fact]
    [SuppressMessage("Microsoft.Naming", "CA2204", Justification = "It's not a word - it's a format string!")]
    public void transition_to_ensures_previous_transition_is_first_completed_before_starting_subsequent_transition()
    {
        var factory = new StateMachineTaskFactory<State>();
        factory.RegisterTransition(State.Starting, State.Started, (ct, o) => TaskUtil.Delay(TimeSpan.FromMilliseconds(10)));
        factory.RegisterTransition(State.Stopping, State.Stopped, (ct, o) => TaskUtil.Delay(TimeSpan.FromMilliseconds(10)));

        var startedAt = DateTime.MinValue;
        var stoppedAt = DateTime.MinValue;
        var startedTask = factory.TransitionTo(State.Started).ContinueWith(x => startedAt = DateTime.UtcNow, TaskContinuationOptions.ExecuteSynchronously);
        var stoppedTask = factory.TransitionTo(State.Stopped).ContinueWith(x => stoppedAt = DateTime.UtcNow, TaskContinuationOptions.ExecuteSynchronously);

        Assert.True(Task.WaitAll(new Task[] { startedTask, stoppedTask }, TimeSpan.FromSeconds(3)), "Timed out waiting for tasks to complete.");
        Assert.True(stoppedAt > startedAt, "stoppedAt is " + stoppedAt.Millisecond + " and startedAt is " + startedAt.Millisecond + ", difference is " + (stoppedAt - startedAt).ToString());
    }

    [Fact]
    public void transition_to_can_be_canceled_before_transition_takes_place()
    {
        var factory = new StateMachineTaskFactory<State>();
        var cancellationTokenSource = new CancellationTokenSource();
        factory.RegisterTransition(State.Starting, State.Started, (ct, o) => TaskUtil.FromResult(true));

        cancellationTokenSource.Cancel();
        var startedTask = factory.TransitionTo(State.Started, cancellationTokenSource.Token);

        try
        {
            startedTask.Wait();
            Assert.True(false, "Failed to throw exception.");
        }
        catch (AggregateException ex)
        {
            Assert.Equal(1, ex.InnerExceptions.Count);
            Assert.IsType<OperationCanceledException>(ex.InnerExceptions[0]);
        }
    }

    [Fact]
    public void transition_to_can_be_canceled()
    {
        var factory = new StateMachineTaskFactory<State>();
        var cancellationTokenSource = new CancellationTokenSource();
        factory.RegisterTransition(State.Starting, State.Started, (ct, o) => TaskUtil.FromResult(true));
        factory.RegisterTransition(State.Stopping, State.Stopped, (ct, o) => TaskUtil.Delay(TimeSpan.FromMilliseconds(150)));

        var startedTask = factory.TransitionTo(State.Started, cancellationTokenSource.Token);
        startedTask.ContinueWith(x => cancellationTokenSource.Cancel());
        var stoppedTask = factory.TransitionTo(State.Stopped, cancellationTokenSource.Token);

        startedTask.Wait(TimeSpan.FromSeconds(3));

        try
        {
            stoppedTask.Wait(TimeSpan.FromSeconds(3));
            Assert.True(false, "Failed to throw exception.");
        }
        catch (AggregateException ex)
        {
            Assert.Equal(1, ex.InnerExceptions.Count);
            Assert.IsType<OperationCanceledException>(ex.InnerExceptions[0]);
        }
    }

    [Fact]
    public void transition_to_can_be_forbidden()
    {
        var factory = new StateMachineTaskFactory<State>();
        factory.RegisterTransition(State.Starting, State.Started, (ct, o) => TaskUtil.FromResult(true));
        factory.RegisterTransition(State.Stopping, State.Stopped, (ct, o) => TaskUtil.FromResult(true));

        var startedTask = factory.TransitionTo(State.Started, CancellationToken.None, x => x == State.Undefined);
        var stoppedTask = factory.TransitionTo(State.Stopped, CancellationToken.None, x => x != State.Started);

        startedTask.Wait(TimeSpan.FromSeconds(3));

        try
        {
            stoppedTask.Wait(TimeSpan.FromSeconds(3));
            Assert.True(false, "Failed to throw exception.");
        }
        catch (AggregateException ex)
        {
            Assert.Equal(1, ex.InnerExceptions.Count);
            var ex2 = Assert.IsType<StateTransitionForbiddenException<State>>(ex.InnerExceptions[0]);
            Assert.Equal(State.Stopped, ex2.TargetState);
            Assert.Equal(State.Started, ex2.State);
            Assert.Equal("A transition to state 'Stopped' was forbidden by the validate transition callback.", ex2.Message);
        }
    }

    [Fact]
    public void canceled_transition_reverts_back_to_original_state()
    {
        var factory = new StateMachineTaskFactory<State>();
        var cancellationTokenSource = new CancellationTokenSource();
        factory.RegisterTransition(State.Starting, State.Started, (ct, o) => TaskUtil.FromResult(true));
        factory.RegisterTransition(State.Stopping, State.Stopped, (ct, o) => TaskUtil.Delay(TimeSpan.FromSeconds(3), cancellationTokenSource.Token));

        factory.StateChanged += (s, e) =>
            {
                if (e.NewState == State.Stopping)
                {
                    // cancel the stop
                    cancellationTokenSource.Cancel();
                }
            };

        var startedTask = factory.TransitionTo(State.Started);
        var stoppedTask = factory.TransitionTo(State.Stopped, cancellationTokenSource.Token);

        startedTask.Wait(TimeSpan.FromSeconds(3));

        try
        {
            stoppedTask.Wait(TimeSpan.FromSeconds(3));
            Assert.True(false, "Failed to throw exception.");
        }
        catch (AggregateException ex)
        {
            Assert.Equal(1, ex.InnerExceptions.Count);
            Assert.IsType<OperationCanceledException>(ex.InnerExceptions[0]);
            Assert.Equal(State.Started, factory.State);
        }
    }

    [Fact]
    public void failed_transition_reverts_back_to_original_state()
    {
        var factory = new StateMachineTaskFactory<State>();
        factory.RegisterTransition(State.Starting, State.Started, (ct, o) => TaskUtil.FromResult(true));
        factory.RegisterTransition(State.Stopping, State.Stopped, (ct, o) => { throw new InvalidOperationException("Something went wrong"); });

        var startedTask = factory.TransitionTo(State.Started);
        var stoppedTask = factory.TransitionTo(State.Stopped);

        startedTask.Wait(TimeSpan.FromSeconds(3));

        try
        {
            stoppedTask.Wait(TimeSpan.FromSeconds(3));
            Assert.True(false, "Failed to throw exception.");
        }
        catch (AggregateException ex)
        {
            Assert.Equal(1, ex.InnerExceptions.Count);
            Assert.IsType<InvalidOperationException>(ex.InnerExceptions[0]);
            Assert.Equal(State.Started, factory.State);
        }
    }

    [Fact]
    public void state_change_is_raised_as_state_changes()
    {
        var factory = new StateMachineTaskFactory<State>(State.Stopped);
        factory.RegisterTransition(State.Starting, State.Started, (ct, o) => TaskUtil.FromResult(true));
        factory.RegisterTransition(State.Stopping, State.Stopped, (ct, o) => TaskUtil.FromResult(true));
        var stateChanges = new List<StateChangedEventArgs<State>>();
        factory.StateChanged += (s, e) => stateChanges.Add(e);

        factory.TransitionTo(State.Started).Wait(TimeSpan.FromSeconds(1));
        factory.TransitionTo(State.Stopped).Wait(TimeSpan.FromSeconds(1));
        factory.TransitionTo(State.Started).Wait(TimeSpan.FromSeconds(1));
        factory.TransitionTo(State.Stopped).Wait(TimeSpan.FromSeconds(1));

        Assert.Equal(8, stateChanges.Count);
        Assert.Equal(State.Stopped, stateChanges[0].OldState);
        Assert.Equal(State.Starting, stateChanges[0].NewState);
        Assert.Equal(State.Starting, stateChanges[1].OldState);
        Assert.Equal(State.Started, stateChanges[1].NewState);
        Assert.Equal(State.Started, stateChanges[2].OldState);
        Assert.Equal(State.Stopping, stateChanges[2].NewState);
        Assert.Equal(State.Stopping, stateChanges[3].OldState);
        Assert.Equal(State.Stopped, stateChanges[3].NewState);
        Assert.Equal(State.Stopped, stateChanges[4].OldState);
        Assert.Equal(State.Starting, stateChanges[4].NewState);
        Assert.Equal(State.Starting, stateChanges[5].OldState);
        Assert.Equal(State.Started, stateChanges[5].NewState);
        Assert.Equal(State.Started, stateChanges[6].OldState);
        Assert.Equal(State.Stopping, stateChanges[6].NewState);
        Assert.Equal(State.Stopping, stateChanges[7].OldState);
        Assert.Equal(State.Stopped, stateChanges[7].NewState);
    }

    [Fact]
    public void state_gets_the_current_state()
    {
        var factory = new StateMachineTaskFactory<State>(State.Stopped);
        factory.RegisterTransition(State.Starting, State.Started, (ct, o) => TaskUtil.Delay(TimeSpan.FromMilliseconds(100)));
        factory.RegisterTransition(State.Stopping, State.Stopped, (ct, o) => TaskUtil.Delay(TimeSpan.FromMilliseconds(100)));

        var task = factory.TransitionTo(State.Started);
        Assert.Equal(State.Starting, factory.State);
        task.Wait(TimeSpan.FromSeconds(3));
        Assert.Equal(State.Started, factory.State);
        task = factory.TransitionTo(State.Stopped);
        Assert.Equal(State.Stopping, factory.State);
        task.Wait(TimeSpan.FromSeconds(3));
        Assert.Equal(State.Stopped, factory.State);
    }
}