异步/等待中的重入

本文关键字:等待 异步 | 更新日期: 2023-09-27 18:34:39

我有一个按钮,它有一个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

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

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

如果我在这里使用同步方法,它会按预期工作。计时器时钟周期事件仅在第一次调用完成后运行。

有人可以开导我吗?

异步/等待中的重入

由于这两个调用都在 UI 线程上运行,因此代码在传统意义上是"线程安全的" - 不会有任何异常或损坏的数据。

但是,是否存在合乎逻辑的竞争条件?确定。您可以轻松地拥有此流程(或任何其他流程(:

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

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

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

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

不过,在这种情况下,似乎简单地避免在当前正在运行时启动新更新就足够了:

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

我可以想到多种方法来解决这个问题

1 不要处理它

就像 i3arnon 说的那样,同时运行对方法的多个调用可能不是问题。这完全取决于更新方法的实现。就像你写的那样,这与你在真正的多线程并发中面临的问题非常相似。如果同时运行多个异步操作对于这些方法不是问题,则可以忽略重入问题。

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

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

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

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

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

4 对请求进行排队

如果实际运行所有更新很重要,并且需要同步,则可以将任务排队,并逐个完成它们。如果您沿着这条路线走下去,请考虑添加某种背压处理......