async/await 继续太快了

async/await continuing too soon

所以我基本上是在尝试将过滤器进程的调用延迟 1.5 秒,以允许用户在需要时键入多个击键。如果键入新的击键,先前等待的任务将被取消并开始等待新的任务:

System.Threading.CancellationTokenSource token = new System.Threading.CancellationTokenSource();

private async void MyTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
  token.Cancel();

  await System.Threading.Tasks.Task.Delay(1500, token.Token);
  this.filterText = (sender as TextBox).Text;
  (this.Resources["CVS"] as CollectionViewSource).View.Refresh();

  //Earlier I had tried this variant too:
  //System.Threading.Tasks.Task.Delay(500, token.Token).ContinueWith(_ =>
  //{
  //  this.filterText = (sender as TextBox).Text;
  //  (this.Resources["CVS"] as CollectionViewSource).View.Refresh();
  //});
}

但是过滤过程(View.Refresh() 行)在第一次击键时立即命中,无需等待。我的印象是,在令牌上调用 Cancel 会杀死 Delay(),从而也会杀死继续任务,然后再植入下一个任务,但显然这个方案不起作用。

我错过了什么?

如果这对任何人有帮助,那么以下内容对我来说是正确的。我的错误是我错误地认为 CancellationTokenSource 是一个信号设备,可以多次使用。显然不是这样:

private System.Threading.CancellationTokenSource cts = new System.Threading.CancellationTokenSource();
private async void TenantsFilter_TextChanged(object sender, TextChangedEventArgs e)
{
  cts.Cancel();
  cts = new System.Threading.CancellationTokenSource();

  try
  {
    await System.Threading.Tasks.Task.Delay(1500, cts.Token);
    this.filterText = (sender as TextBox).Text;
    (this.Resources["CVS"] as CollectionViewSource).View.Refresh();
  }
  catch(System.Threading.Tasks.TaskCanceledException ee)
  {
  }
}

张贴在这里作为我自己的记录,只是为了让其他人检查我仍然没有做错任何事情。

处理此问题的正确方法不是使用 Task.Delay 和例外(因为例外是针对特殊情况),而是使用 TimerTimer.Elapsed 事件。

例如

using Timer = System.Windows.Forms.Timer;

private readonly Timer timer = new Timer();
private static string newText = "";

public Form1()
{
    timer.Interval = 1500;
    timer.Tick += OnTimedEvent;
}

private void MyTextBox_TextChanged(object sender, EventArgs e)
{
    timer.Stop(); // sets the time back to 0
    newText = (sender as TextBox).Text; // sets new text
    timer.Start(); // restarts the timer
}

private void OnTimedEvent(Object source, EventArgs e)
{
    filterText = newText;
    (Resources["CVS"] as CollectionViewSource).View.Refresh();
}

(不确定这是否 100% 正确,但您明白了要点。)


与评论中的讨论相关的旧片段。

正如 post 所说:这不是必需的,因为 Task.Delay 将 link 成为 CancellationToken 的侦听器,因此 .Cancel() 将阻塞直到所有的听众都听到了。

using System.Threading;
using System.Threading.Tasks;

private CancellationTokenSource cts = new CancellationTokenSource();
private Task delayTask;

private async void TenantsFilter_TextChanged(object sender, TextChangedEventArgs e)
{
  cts.Cancel();
  if (delayTask != null) {
    try{await delayTask;}
    catch(TaskCanceledException){}
  }
  cts = new CancellationTokenSource();

  try
  {
    delayTask = Task.Delay(1500, cts.Token);
    await delayTask;
    this.filterText = (sender as TextBox).Text;
    (this.Resources["CVS"] as CollectionViewSource).View.Refresh();
  }
  catch(TaskCanceledException)
  {
  }
}