Reputation: 35450
So I'm basically trying to delay the invocation of filter process by 1.5 seconds to allow user to type multiple keystrokes in case they want to. If a new keystroke is typed, previously waiting task is cancelled and a new one starts waiting:
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();
//});
}
But the filter process (View.Refresh()
line) hits immediately on first keystroke without waiting. My impression was that calling Cancel
on the token would kill Delay()
and thereby the continuation task too, before planting the next one, but apparently this scheme doesn't work.
What am I missing?
Upvotes: 0
Views: 456
Reputation: 11291
The proper way to handle this is not with Task.Delay
and exceptions (as exceptions are for exceptional circumstances), but using a Timer
with the Timer.Elapsed
event.
E.g.
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();
}
(Not sure this is 100% correct, but you get the gist.)
Old snippet relating to the discussions in the comments.
As the post says: this is not needed, as Task.Delay
will link a listener to the CancellationToken
, thus .Cancel()
will block until all listeners have heard it.
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)
{
}
}
Upvotes: 1
Reputation: 35450
If this helps anyone, the following is correctly working for me. My mistake was that I incorrectly assumed that CancellationTokenSource is a signaling device and could be used multiple times. That is apparently not the case:
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)
{
}
}
Posting it here for my own record and just to let others check I'm still not doing anything wrong.
Upvotes: 1