我在等待一系列状态变化。我的问题是,我想等待一个连续的状态,只有当先验符合预期时,这不起作用......因为似乎代码无条件地等待所有状态。 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。
答案 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(); }
}
}
}