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

时间:2012-07-24 15:56:57

标签: 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(() => DoShutdown());
    }
}

上述问题是,如果对StartAsyncStopAsync进行多次调用,则会创建多个任务来启动/停止服务。实际上,人们只希望一次启动一个启动/停止任务。为此,我通常会这样做:

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
    }
}

现在这虽然有效,但它有点难看。在我试图将这种模式封装成小而可重复的东西之前,我已经走得太远了,我想知道是否有一种已经建立的方式这样做我不知道?

PS。我通过让StartAsync等待任何未完成的StopAsync电话来进一步扩展这个想法,反之亦然。这意味着任何时候都只能进行一次启动或停止操作。通过任务组合很容易做到这一点。如果我能首先发现是否已经建立了完全实现上述目标的方法,那么我可以弄清楚它是否适合这种扩展行为。

4 个答案:

答案 0 :(得分:1)

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

StoppingStopped提供相同内容。

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

<强>更新
...... 可以的另一种方式是存储和检查Cancellation Token。有关如何使用CancellationToken的无数样本。这只是我的头脑,而不是确认这是最好的方式。

答案 1 :(得分:1)

我终于在我的代码库中解决了这个问题,并想到我会在这里分享我的解决方案。快速解释:我有一个StateMachineTaskFactory<T>类,其中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
    }
}

除了上面的示例用法之外,还有更多的功能和灵活性,但它可能是正常的用例。

对于以下代码墙感到抱歉。我删除了API文档以提高可读性。我没有包含几个实用程序类,但它们非常明显。

[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);
    }
}

答案 2 :(得分:0)

可能不是你想要的,但是如果将它们排队而不是忽略是意图(或者是可接受的),那么一个相对简单的选择就是使用the limited-concurrency scheduler来进行这两个StartNew调用。

获取你正在寻找的效果所需的代码超过了必要的代码,但是可以让你利用已经编写的代码而不是编写自己的代码。 :)

答案 3 :(得分:-1)

好吧,如果你想一次只激活一个类的一个实例,你应该考虑使用单例模式。

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

public class Service
{
    private object oLock;
    private bool running;
    private Thread threadTask;
    private AutoResetEvent taskStarted;

    public bool IsRunning { get { return this.running; } }

    public Service()
    {
        oLock = new object();
        taskStarted = new AutoResetEvent(false);
        running = false;
    }

    public void Start()
    {
        // If we can not acquire the lock, then there is a Start or Stop operation 
        // in progress, so better leave. Also leave if already running.
        if (running || !Monitor.TryEnter(oLock))
            return;

        // Set running flag to prevent other threads to start service again
        running = true;

        // Create and start the thread
        threadTask = new Thread(new ThreadStart(Task));
        threadTask.IsBackground = true;
        threadTask.Start();

        // Wait until the task execution begins. This is optional
        taskStarted.WaitOne();

        // Release the lock
        Monitor.PulseAll(oLock);
        Monitor.Exit(oLock);
    }

    public void Stop()
    {
        // If we can not acquire the lock, then there is a Start or Stop operation 
        // in progress, so better leave. Also leave if not running.
        if(!running || !Monitor.TryEnter(oLock))
            return;

        // Clear the running task to prevent reentrancy
        running = false;

        // Here we can abort the thread. This is optional an depends on task
        threadTask.Abort();

        // Wait until the thrad finish
        threadTask.Join();

        // Clear
        threadTask = null;

        // Release the lock
        Monitor.PulseAll(oLock);
        Monitor.Exit(oLock);
    }

    protected virtual void Task()
    {
        // Setup task and allocate resources
        taskStarted.Set();

        // Execute the task poll
        while (running)
        {
        }

        // Finish the task and release resources
    }
}

其中一个技巧是使用Monitor类而不是lock语句,因此尝试启动服务的第一个线程将获胜,而其他线程将用于已经启动或停止的服务。如果您打算颠倒

的顺序,请小心
if (Monitor.TryEnter() || running)
    return;

你必须调用Monitor.Exit或你的应用程序将死锁。

当然,您可以更改枚举的运行布尔值,该枚举表示空闲,开始,运行和停止状态。

同样最好让类抽象化并让继承者覆盖在/而不是while语句中调用的方法。

希望这会有所帮助。 最好的问候。