如何在 C# 中创建可重新触发的延迟?

How to create a Re-triggerable delay in C#?

我有一个用户名字符串 属性,通过 MVVM 连接到一个文本条目,无论何时设置它,我都会调用一个方法来检查服务器以查看用户名是否可用。现在我不希望每次键入一个键时都调用它,我想要的是让它检测用户何时停止键入。

目前这是我的代码...

private string _username;
public string Username
{
    get => _username;
    set
    {
        SetProperty(ref _username, value);
        Task.Run(async () => await CheckUsernameExists(1000));
    }
}

然后是 CheckUsernameExists() 方法...

/// <summary>
/// Checks if the username already exists
/// </summary>
/// <returns></returns>
public async Task CheckUsernameExists(int timeoutInMilliseconds)
{
    await Task.Delay(timeoutInMilliseconds);

    try
    {...

但是这不会重新触发它,它只会延迟调用 1 秒。

我从 Unreal Engine 4 得到了可重触发延迟的想法 https://docs.unrealengine.com/en-US/BlueprintAPI/Utilities/FlowControl/RetriggerableDelay/index.html

UE4 发生的情况是,该函数被调用一次。然后它在可重触发延迟上启动定时器。如果在该计时器为 运行 时再次调用该函数,它将重新启动计时器。然后,只有当它完成时,它才会调用它之​​后的代码。

有谁知道如何在 C# 中执行此操作?谢谢!

这是开始延迟检查每种类型的代码,但只有最后一次调用才真正检查用户名:

private int _checkUsernameCalls = 0;
public string Username
{
    get => _username;
    set
    {
        SetProperty(ref _username, value);
        _checkUsernameCalls++;
        Task.Delay(TimeSpan.FromSeconds(1)).ContinueWith(t => CheckUsernameExists());
    }
}

void CheckUsernameExists()
{
    if (--_checkUsernameCalls > 0)
    {
        return;
    }
    // actual checks...
}

...what I want instead is for it to detect when the user has stopped typing.

仅在用户停止输入后调用检查服务器的函数实际上是一个非常简单的逻辑。每当用户在您的控件中键入一个字符(比方说一个文本框)时,您可以通过启动 System.Timers.Timer 来做到这一点。将定时器设置为在定时器到达时触发 Elapsed 事件,比方说 150 毫秒。最后,您可以订阅 Elapsed 事件并分配在服务器上发出请求的函数。每次用户在文本框中输入新内容时,计时器都会重置,您将获得所需的行为(因为 Elapsed 事件只会在计时器达到您设置的延迟时触发)。

standard way的做法是使用CancellationTokenSource对象。每次 属性 更改时创建一个新的,在取消前一个之后。

private string _username;
public string Username
{
    get => _username;
    set
    {
        SetProperty(ref _username, value);
        CheckUsernameExistsAsync(value, 1000);
    }
}

private CancellationTokenSource _cts;

private async void CheckUsernameExistsAsync(string username, int timeout)
{
    try
    {
        _cts?.Cancel();
        _cts = new CancellationTokenSource();
        var cancellationToken = _cts.Token;
        await Task.Delay(timeout, cancellationToken);
        // Check if the username exists
        // Use the same cancellationToken if the API supports it
    }
    catch (TaskCanceledException)
    {
        // Ignore the exception
    }
    catch (Exception)
    {
        // Log the exception
    }
}

警告: class CancellationTokenSource 是一次性的,但我没有在上面的例子中处理它。我不确定这是否是一个大问题。可能不是。这是一个相关的问题:When to dispose CancellationTokenSource?