C# 异步/等待中的可重入性?

C# 异步/等待中的可重入性?,c#,.net,wpf,multithreading,async-await,C#,.net,Wpf,Multithreading,Async Await,我有一个按钮,它有一个async处理程序,它调用异步方法等待。下面是它的样子: private async void Button1_OnClick(object sender, RoutedEventArgs e) { await IpChangedReactor.UpdateIps(); } 以下是IpChangedReactor.UpdateIps()的外观: public async Task UpdateIps() { await UpdateCurrentIp();

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

private async void Button1_OnClick(object sender, RoutedEventArgs e)
{
    await IpChangedReactor.UpdateIps();
}
以下是
IpChangedReactor.UpdateIps()
的外观:

public async Task UpdateIps()
{
    await UpdateCurrentIp();
    await UpdateUserIps();
}
它一直是异步的。
现在我有了一个
dispatchermer
,它在tick事件中反复调用
wait-IpChangedReactor.UpdateIps

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

我知道异步方法不一定在单独的线程上运行。然而,在这个案例中,这是相当令人困惑的

如果我在这里使用同步方法,它会像预期的那样工作。计时器滴答声事件将仅在第一次调用完成后运行


有人能告诉我吗?

因为两个调用都在UI线程上运行,所以代码在传统意义上是“线程安全的”——不会有任何异常或损坏的数据

然而,是否存在逻辑竞态条件?当然您可以很容易地获得此流(或任何其他流):

从方法名称来看,这似乎不是一个真正的问题,但这取决于这些方法的实际实现

通常,您可以通过使用
信号量lim
异步锁来同步调用来避免这些问题:

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

if (_isUpdating) return;

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

我可以想出很多方法来处理这个问题

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

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

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

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

4对请求进行排队
如果实际运行所有更新很重要,并且您需要同步,那么您可以对任务进行排队,并逐个完成任务。如果你在这条路径上添加一些反压处理……

你正在努力寻找的术语是“”:“在计算中,一个计算机程序或子例程被称为可重入的,如果它可以在执行的中间被中断,然后在它的先前调用完成执行之前安全地再次调用(“重新进入”)。”比如
if(\u isupdated)return_isUpdating=true
在方法开始时,isUpdating=false
在方法结束时?如果您在计时器中工作的时间超过计时器的滴答声,则使用锁实际上并不合适。您可能希望在前一个调用完成之前不要启动新的调用,否则您只会得到一个不断增加的待办事项。@Servy这是真的,有办法处理它。但实际上,你不应该一开始就使用计时器。你应该使用一个周期性的任务来执行,然后通过
task.Delay
@i3arnon等待。使用计时器没有什么错。使用
Delay
会有不同的语义。哪种语义实际上更好取决于实际需求是什么。计时器仍然有它们的位置,完全有可能它在这里是合适的。@EmperueRaiman如果你不关心操作将运行多少次,只关心它最近运行(即刷新),那么它也应该工作。
using (await _asyncLock.LockAsync())
{
    await IpChangedReactor.UpdateIps();
}
if (_isUpdating) return;

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