异步等待一系列状态

时间:2013-11-25 18:20:38

标签: c# asynchronous expression c#-5.0

我在等待一系列状态变化。我的问题是,我想等待一个连续的状态,只有当先验符合预期时,这不起作用......因为似乎代码无条件地等待所有状态。 buttonRun_Click中的布尔表达式在全部按预期运行时起作用,但如果不是,则表达式为false,但似乎await仍处于活动状态。一个症状是表单在关闭时不会退出,因为任务仍在运行。

    private void StateChange(Information info)
    {
        // If we are expecting a certain state, and get it, inform our registrant
        this.gState = info;
        this.gStateTask.SafeTrySetResult(this.gStateExpected(this.gState));
    }

    private async Task<bool> ExpectStateAsync(Predicate<Information> expect)
    {
        if (expect(this.gState)) { return true; }
        this.gStateExpected = expect;
        this.gStateTask = new TaskCompletionSource<bool>();

        try
        {
            await this.gStateTask.Task;
            return this.gStateTask.Task.Result;
        }
        catch (TaskCanceledException)
        {
            return false;
        }
    }

    private async void buttonRun_Click(object sender, EventArgs e)
    {
        Command(Request.Run);
        if (await ExpectStateAsync(s => s.Initiated)
            && await ExpectStateAsync(s => s.Scanning)
            && await ExpectStateAsync(s => s.Expect(Status.Attached)))
        {
            ShowError("Job complete");
            // Job completed successfully
        }
    }

如何重构此代码,以便我可以异步等待第一个状态更改,如果我得到它,等待第二个,等等?如果状态在任何时候变为不期望的状态,则布尔值应该短路并返回false。

1 个答案:

答案 0 :(得分:0)

基于我收到的良好帮助,我能够创建一个我认为值得共享的泛型类 - 包括用于调试的代码 - 这使得等待一系列异步状态转换变得容易。这是经过测试的,没有其他依赖项的工作代码,除了带有接口ILogger的标准记录器(当然还有C#4.5)。

在我的Windows窗体中使用它可以使表单非常灵敏 - 例如,即使在等待其他任务时,我也非常满意它的绘制速度。

我做了两个实例,一个是工作请求:

_jobs = new ClassCommandStateProgression<JobState>();

if (await _jobs(CommandExpectedStatesAsync(() => Run(),
    s => s.IsAvailable,
    s => s.IsCleanedUp,
    s => s.IsFinished
    )
{
    Console.Writeline("Job completed successfully!");
}

和请求确认:

this.acknowledge = new ClassCommandStateProgression<CommandReplyMessage>();

if (!await this.acknowledge.CommandExpectedStatesAsync(
    () => Command(this, request, parameters),
    s => s.MessageType == request))
{
    ShowError(string.Format("Unexpected reply: {0} instead of {1}", this.State.MessageType, request));
}

接收响应的事件处理程序为两个实例调用StateTransition()。

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace States
{
    /// <summary>
    /// Execute a command while asynchronously watching to see that a series of state transitions (of an arbitrary type) will be executed, 
    /// </summary>
    /// <example>
    /// A single instance is enough for each type of transition.
    /// <code>
    /// this.Expectations = new ClassCommandStateProgression();
    /// </code>
    /// Invocations should not be interleaved, even though if the state progression does not proceed as expected, the object will be cleaned up.
    /// <code>
    /// if (await this._Expectations.CommandExpectedStatesAsync(command, {state predicates}) { ... }
    /// </code>
    /// It is simple to inform the instance of state changes.
    /// <code>
    /// this._Expectations.StateTransition(state);
    /// </code>
    /// </example>
    public class ClassCommandStateProgression<T> where T : class
    {
        // Pair a TaskCompletionSource with a predicate for its completion
        private struct ExpectedTask
        {
            public readonly TaskCompletionSource<bool> _Task;
            public readonly Predicate<T> _Predicate;

            public ExpectedTask(Predicate<T> predicate)
            {
                this._Task = new TaskCompletionSource<bool>();
                this._Predicate = predicate;
            }
        }

        private Queue<ExpectedTask> _Q = new Queue<ExpectedTask>();
        private bool _Result;

        // Debug logging
        private ILogger _Logger;
        private Func<T, string> _IdentifyState;
        private static int _NestingLevel;       // Track waiting

        /// <summary>
        /// No logging by default.
        /// </summary>
        public ClassCommandStateProgression()
        {
        }

        /// <summary>
        /// For debugging purposes, identify when the state changes.
        /// </summary>
        /// <param name="logger">The logger to use.</param>
        /// <param name="identifyState">A delegate to describe the state.</param>
        public ClassCommandStateProgression(ILogger logger, Func<T, string> identifyState)
        {
            this._Logger = logger;
            this._IdentifyState = identifyState;
        }

        /// <summary>
        /// Execute a command that should cause a series of asynchronous state transitions.
        /// </summary>
        /// <param name="command">The command to execute.</param>
        /// <param name="predicates">A list of predicates describing the expected states.</param>
        /// <returns>True if all state transitions took place as expected.</returns>
        public async Task<bool> CommandExpectedStatesAsync(Action command, params Predicate<T>[] predicates)
        {
            this._Result = true;

            if (null != predicates && predicates.Length > 0)
            {
                foreach (var predicate in predicates)
                {
                    this._Q.Enqueue(new ExpectedTask(predicate));
                }
            }

            return await AwaitExpectedStatesAsync(command).ConfigureAwait(false);
        }

        /// <summary>
        /// Execute a command that should cause a series of asynchronous state transitions,
        /// perhaps being originally in the first of the states.
        /// </summary>
        /// <param name="state">The original state.</param>
        /// <param name="command">The command to execute.</param>
        /// <param name="predicates">A list of predicates describing the expected states.</param>
        /// <returns>True if all state transitions took place as expected.</returns>
        public async Task<bool> CommandExpectedStatesAsync(T state, Action command, params Predicate<T>[] predicates)
        {
            this._Result = true;

            if (null != predicates && predicates.Length > 0)
            {
                bool alreadyInState = predicates[0](state);
                foreach (var predicate in predicates)
                {
                    if (alreadyInState)
                    {
                        if (null != this._Logger) { this._Logger.Debug("Already true => {0}", this._IdentifyState(state)); }

                        alreadyInState = false;
                        continue;
                    }
                    this._Q.Enqueue(new ExpectedTask(predicate));
                }
            }

            return await AwaitExpectedStatesAsync(command).ConfigureAwait(false);
        }

        // Await the queued states.
        private async Task<bool> AwaitExpectedStatesAsync(Action command)
        {
            try
            {
                if (null != this._Logger) { this._Logger.Debug("Waiting for {0} state transitions.", this._Q.Count); }

                // The caller must catch any exceptions thrown by the command.
                command();

                while (this._Result && this._Q.Count > 0)
                {
                    try
                    {
                        Interlocked.Increment(ref gNestingLevel);
                        if (null != this._Logger) { this._Logger.Debug("Awaiting managed thread {0}; at nesting {1}.", Thread.CurrentThread.ManagedThreadId, gNestingLevel); }

                        var waitFor = this._Q.Peek()._Task;
                        await waitFor.Task.ConfigureAwait(false);
                        this._Result = waitFor.Task.Result;

                        Interlocked.Decrement(ref gNestingLevel);
                        if (null != this._Logger) { this._Logger.Debug("Back from await; at nesting {0}.", gNestingLevel); }
                    }
                    catch (TaskCanceledException)
                    {
                        return false;
                    }
                }

                return this._Result;
            }
            finally
            {
                this._Q.Clear();
            }
        }

        /// <summary>
        /// Transition to the next state.
        /// </summary>
        /// <param name="state">A state object of the parameterized type <typeparamref name="T"/>.</param>
        public void StateTransition(T state)
        {
            if (this._Q.Count > 0)
            {
                ExpectedTask et = this._Q.Dequeue();
                this._Result = this._Result && et._Predicate(state);
                if (null != this._Logger)
                {
                    if (!this._Result) { this._Logger.Warn("UNEXPECTED! => {0}", this._IdentifyState(state)); }
                    else { this._Logger.Debug("{0}{1}", this._IdentifyState(state), this._Q.Count > 0 ? ", still waiting..." : "."); }
                }
                et._Task.TrySetResult(this._Result);
            }
        }

        /// <summary>
        /// Stop waiting.
        /// </summary>
        public void Cancel()
        {
            if (this._Q.Count > 0) { this._Q.Peek()._Task.TrySetCanceled(); }
        }
    }
}