【发布时间】:2020-04-17 10:58:41
【问题描述】:
在这个勇敢的新异步世界中,我发现自己一次又一次地需要一种可等待的方法来等待某物处于某个状态或满足某个条件。例如(伪代码)
await (myStateMachine.State == StateEnum.Ready);
await (myDownloadProgress == 100.0);
await (mySpiDeviceFifoLEvel != 0);
出现这些情况是因为我需要推迟一些异步启动的代码,直到代码的另一部分达到某个状态。例如,用户启动了 UI 的新部分,但后台线程仍在尝试与某个硬件建立通信。或者控制一个硬件的状态机需要等到另一个控制另一个硬件的状态机达到一定的就绪状态。
我主要想出了一些奇妙而古怪的方法来实现这一点,并且在这样做的过程中注意到某些模式正在出现,因此自然的进展是为我们编写一些帮助类/泛型以在可重用的情况下执行此类行为时尚。
在我走这条路之前,肯定有其他人在解决这类问题,所以如果有人知道一个久经考验的模式或推荐的这样做的方法,我很感兴趣。我在 WWW 上进行了一些搜索,但没有找到任何特别确定的内容。这个SO question 涉及到这个主题,但操作员要求不同的原因。这个SO question 要求相同类型的东西,但特定于任务进度。
到目前为止我实现这一目标的方式
1。不要这样做!使用事件
当我控制正在变化的源(例如状态机的状态)时,我经常说服自己我做错了,而不是等待实现价值,我应该让生产者(状态机)当我的条件达到时生成一个事件。然后任何侦听器都可以使用AutoResetEvent 或ManualResetEvent 等待处理程序
{
myStateMachine.OnMyConditionAchieved += OnConditionAchievedEventHandler;
myEvent = new AutoResetEvent(false);
myEvent.WaitOne();
}
void OnConditionAchievedEventHandler(object sender, EventArgs e)
{
myEvent.Set();
}
这样做的缺点是我真的不想在我的生产者代码中乱扔特定于消费者需求的事件。
2。使用事件,编码开销与性能权衡
如果还没有一个方便的事件可以挂接到 (1) 中,那么生产者将永远被修改以满足消费者的需求。因此,显而易见的自然进程是使用INotifyPropertyChanged 模式之类的东西。这样一来,生产者就没有无限的扩展,消费者这样做:
void StateMachine_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.Property == "State")
{
if (myStateMachine.State == State.TheStateThatIWant)
{
myEvent.Set();
}
}
}
这感觉像是一场胜利,因为我经常使用 NotifyPropertyChanged 系统 - 它是 DataBinding 所必需的,因此要添加的代码更少,但是,我们正在监听生产者中的每个更改以过滤掉,这感觉很脏想要的条件——肯定有更好的方法吗?!
3。使用任务和投票(呃)
启动一个检查状态的任务,如果条件未无限期满足或直到任务被取消,则休眠。呼叫者,然后等待任务完成。满足或取消条件时任务完成。
优点 - 使代码简洁,尤其是在使用 Task.Run(() => ... ) lambda 方法时,可以利用通常也需要的任务取消技术(令牌、超时等) 缺点 - 轮询感觉很脏,构建一个全新的任务来完成如此简单的工作似乎有点笨拙
4。使用任务并等待事件
比投票好,对吧?但是遇到与 1) 和 2) 相同的问题,即需要一个适当的事件来挂钩,因此 2) (INotifyPropertyChanged) 比 1) 更常见。因此,实现通常以启动任务、等待 ManualResetEvent、监听 PropertyChanged 并过滤更改、触发事件、从任务返回。
5。圣杯
我不是 100% 确定,但确实是 1) 轻量级 2) 允许在启动等待时指定条件 3) 如果有 10,000 个事物在等待各种属性以达到特定值,则不会成为巨大的资源负担 4) 清洁,即正确处理资源
MagicValueWaiter waitForValue = new MagicValueWaiter(MyStateMachine, nameof(State), (s) => (s > 4) && (s < 8));
await waitForInit.WaitAsync();
或
await ValueWaiter.WaitAsync(MyObject, nameof(MyPropertyorField), (s) => (s == States.Init);
所以基本上,一个通用类/方法用于等待给定对象的给定属性或字段以 lambda 返回 bool 的形式满足某个给定条件。
这种方法乍一看可能暗示了一种轮询技术,但是,如果我强制 MyObject 符合必须实现 INotifyPropertyChanged 或一些自定义基类来支持这种行为,例如ISupportValueWaiting 然后我们可以挂钩一些常见的行为,例如MyObject 上的事件并避免轮询。
我缺少任何明显的解决方案吗?有人对如何做到这一点有任何新颖的想法吗?还是我的cmets?
【问题讨论】:
-
你遇到过TaskCompletionSource吗?
-
你看过TaskCompletionSource吗?
-
我使用了TaskCompleteSource,但主要是为了将值返回给等待方法,所以我没有立即看到它如何应用于这个问题,更仔细地考虑它,我看到生产者可以消费者提供的 TCS 上的 SetResult(null),这将为我们提供等待消费者和释放生产者行为所需的行为。它要求生产者支持“ICanReleaseConsumers”行为并支持任意条件需要一些思考,但我想还有一种方法可以构建 lambda 条件 - 我会玩 - 谢谢。
标签: c# .net asynchronous async-await