【问题标题】:How to create a Re-triggerable delay in C#?如何在 C# 中创建可重新触发的延迟?
【发布时间】:2019-08-26 18:45:03
【问题描述】:

我拥有的是用户名的字符串属性,通过 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 秒。

我从虚幻引擎 4 中得到了可重新触发延迟的想法 https://docs.unrealengine.com/en-US/BlueprintAPI/Utilities/FlowControl/RetriggerableDelay/index.html

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

有谁知道如何在 C# 中做到这一点?谢谢!

【问题讨论】:

  • 这是在 WPF 中吗?如果有,是什么版本?

标签: c# asynchronous task delay


【解决方案1】:

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
    }
}

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

【讨论】:

    【解决方案2】:

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

    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...
    }
    

    【讨论】:

    • Note: 您可能需要在访问 _checkUsernameCalls 变量(或使其成为线程安全属性)时使用 Lock(),因为它将是多线程环境。
    【解决方案3】:

    ...我想要的是它检测用户何时停止输入。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-31
      • 2023-04-01
      • 2015-02-15
      • 2021-04-02
      • 1970-01-01
      相关资源
      最近更新 更多