在async / await中重入?

我有一个按钮,它有一个async处理程序,它在异步方法上调用等待。 这是它的样子:

 private async void Button1_OnClick(object sender, RoutedEventArgs e) { await IpChangedReactor.UpdateIps(); } 

以下是IpChangedReactor.UpdateIps()外观:

 public async Task UpdateIps() { await UpdateCurrentIp(); await UpdateUserIps(); } 

它一直是异步的。
现在我有一个DispatcherTimer ,它在tick事件中重复调用await IpChangedReactor.UpdateIps

假设我点击了按钮。 现在事件处理程序在UpdateIpsUpdateIps并返回到调用者,这意味着WPF将继续执行其他操作。 在此期间,如果计时器被触发,它将再次调用UpdateIps ,现在两个方法将同时运行。 所以我看到的方式是它类似于使用2个线程。 竞争条件会发生吗? (我的一部分说不,因为它都在同一个线程中运行。但它令人困惑)

我知道异步方法不一定在单独的线程上运行。 但是,在这种情况下,它非常令人困惑。

如果我在这里使用同步方法,它将按预期工作。 计时器滴答事件将仅在第一次呼叫完成后运行。

有人可以开导我吗?

由于两个调用都在UI线程上运行,因此代码在传统意义上是“线程安全的” – 不存在任何exception或损坏的数据。

但是,是否存在合理的竞争条件? 当然。 您可以轻松地拥有此流程(或任何其他流程):

 UpdateCurrentIp() - button UpdateCurrentIp() - Timer UpdateUserIps() - Timer UpdateUserIps() - button 

通过方法名称,它似乎不是真正的问题,但这取决于这些方法的实际实现。

通常,您可以通过使用SemaphoreSlimAsyncLock同步调用来避免这些问题( 如何保护可能在multithreading或异步环境中使用的资源? ):

 using (await _asyncLock.LockAsync()) { await IpChangedReactor.UpdateIps(); } 

但在这种情况下,似乎只是避免在当前运行时启动新更新就足够了:

 if (_isUpdating) return; _isUpdating = true; try { await IpChangedReactor.UpdateIps(); } finally { _isUpdating = false; } 

我可以想出一些处理这个问题的方法

1不要处理它

就像i3arnon所说,对同时运行的方法进行多次调用可能不是问题。 这一切都取决于更新方法的实现。 就像你写的那样,你在真正的multithreading并发中遇到了同样的问题。 如果同时运行多个异步操作对于这些方法不是问题,则可以忽略重入问题。

2阻止计时器,等待运行任务完成

您可以禁用计时器,当您知道正在运行异步任务时,阻止对事件处理程序的调用。 您可以使用简单的状态字段或任何类型的锁定/信令原语。 这可确保您在给定时间只运行一个操作。

3取消任何正在进行的异步操作

如果要取消已运行的任何异步操作,可以使用canceltoken来停止它们,然后启动新操作。 此链接中描述了如何在等待中取消任务?

如果操作需要很长时间才能完成,并且您希望避免花时间完成已经“过时”的操作,这将是有意义的。

4排队请求

如果实际运行所有更新很重要,并且您需要同步,则可以对任务进行排队,并逐个处理它们。 如果沿着这条路走下去,考虑增加一些背压处理……