似乎任务没有完全取消
It seems that the task doesn't get canceled completely
我有一个文本框,可以防止用户写入我用正则表达式指定的不允许的字符,如果用户写入其中任何一个,就会出现一个弹出窗口并在那里停留 5 秒钟,我做了这个使用 Task.Delay(5000, cts.Token)
的持续时间也有一个取消标记,如果用户输入允许的字符,弹出窗口会出现在屏幕上,然后弹出窗口消失,并且该延迟任务被取消和处理,但是如果我写一个不允许的字符,然后立即写一个允许的字符,如果我写得非常快,那么当我写一个不允许的字符打开弹出窗口时,弹出窗口会在不到 5 秒的时间内消失(在随机的一秒内,我认为这是因为延迟任务在那一刻被取消,然后导致弹出窗口消失),这与我没有使用取消令牌取消延迟任务时的问题相同。但是对于我编写的代码,我不知道任务是如何不被完全取消的,或者它可能会被取消但问题是其他的......
CancellationTokenSource cts;
protected async override void OnPreviewTextInput(TextCompositionEventArgs e)
{
if (txtName.IsFocused)
{
if (cts != null && !regex.IsMatch(e.Text))
{
cts.Cancel();
cts.Dispose();
}
cts = new CancellationTokenSource();
if (regex.IsMatch(e.Text))
{
e.Handled = true;
txtName.CaretIndex = txtName.Text.Length;
if (AlertPopup.IsOpen == false)
{
AlertPopup.IsOpen = true;
txtName.Focus();
try
{
await Task.Delay(5000, cts.Token);
}
catch (TaskCanceledException) { }
finally
{
AlertPopup.IsOpen = false;
}
}
}
else
{
AlertPopup.IsOpen = false;
}
base.OnPreviewTextInput(e);
}
}
OnPreviewTextInput()
方法仅在 UI 线程中执行。同样,该方法中 await
之后的延续仅在 UI 线程中执行。 UI 线程一次只能做一件事。这意味着在错误弹出窗口已经显示的情况下,当有新的用户输入并且代码尝试取消该显示(即通过取消延迟的取消标记)时,await
随后导致 AlertPopup.IsOpen
属性 设置为 false
在对 OnPreviewTextInput()
的新调用已 returned 之前不会实际执行继续。
因此,当有新的用户输入时,该输入要么有效,要么无效。如果有效,则将 IsOpen
设置为 false
和 return。稍后再次隐藏弹出窗口是没有问题的。但如果输入无效,弹出窗口的当前状态是 IsOpen
为真,您将跳过尝试显示它、方法 returns 和 then 已取消任务的继续执行,隐藏弹出窗口。
请注意,这里的重要部分是延续发生在方法 return 之后。即使您尝试无条件地显示弹出窗口(即在执行该部分之前不检查 IsOpen
),弹出窗口也会显示然后立即隐藏。
您可以通过使用不同的计时机制(即您可以延长计时器而不必取消和重新启动,例如 System.Threading.Timer
)或使用您现在拥有的机制但保持每次触发无效状态时递增的计数器,每次计时器的等待完成时递减,并且仅当计数器再次达到零时才隐藏弹出窗口。
但实际上,我认为这都不是很好的用户交互。作为用户,我真的开始讨厌定时工具提示,因为它们永远不会显示足够长的时间。 WPF 中一种更常见的技术是使用一种内置验证机制(WPF 中至少有三种不同的验证变体,each with their own pros and cons),然后将元素的常规工具提示值设置为您的错误消息,以便用户只要将鼠标悬停在元素上就可以看到它。
任何类型的用户输入验证都存在障碍和麻烦。但是,如果您使用这些标准之一,至少您不必处理这个计时问题,并且您的用户可能会发现它也更适合他们。 :)
我有一个文本框,可以防止用户写入我用正则表达式指定的不允许的字符,如果用户写入其中任何一个,就会出现一个弹出窗口并在那里停留 5 秒钟,我做了这个使用 Task.Delay(5000, cts.Token)
的持续时间也有一个取消标记,如果用户输入允许的字符,弹出窗口会出现在屏幕上,然后弹出窗口消失,并且该延迟任务被取消和处理,但是如果我写一个不允许的字符,然后立即写一个允许的字符,如果我写得非常快,那么当我写一个不允许的字符打开弹出窗口时,弹出窗口会在不到 5 秒的时间内消失(在随机的一秒内,我认为这是因为延迟任务在那一刻被取消,然后导致弹出窗口消失),这与我没有使用取消令牌取消延迟任务时的问题相同。但是对于我编写的代码,我不知道任务是如何不被完全取消的,或者它可能会被取消但问题是其他的......
CancellationTokenSource cts;
protected async override void OnPreviewTextInput(TextCompositionEventArgs e)
{
if (txtName.IsFocused)
{
if (cts != null && !regex.IsMatch(e.Text))
{
cts.Cancel();
cts.Dispose();
}
cts = new CancellationTokenSource();
if (regex.IsMatch(e.Text))
{
e.Handled = true;
txtName.CaretIndex = txtName.Text.Length;
if (AlertPopup.IsOpen == false)
{
AlertPopup.IsOpen = true;
txtName.Focus();
try
{
await Task.Delay(5000, cts.Token);
}
catch (TaskCanceledException) { }
finally
{
AlertPopup.IsOpen = false;
}
}
}
else
{
AlertPopup.IsOpen = false;
}
base.OnPreviewTextInput(e);
}
}
OnPreviewTextInput()
方法仅在 UI 线程中执行。同样,该方法中 await
之后的延续仅在 UI 线程中执行。 UI 线程一次只能做一件事。这意味着在错误弹出窗口已经显示的情况下,当有新的用户输入并且代码尝试取消该显示(即通过取消延迟的取消标记)时,await
随后导致 AlertPopup.IsOpen
属性 设置为 false
在对 OnPreviewTextInput()
的新调用已 returned 之前不会实际执行继续。
因此,当有新的用户输入时,该输入要么有效,要么无效。如果有效,则将 IsOpen
设置为 false
和 return。稍后再次隐藏弹出窗口是没有问题的。但如果输入无效,弹出窗口的当前状态是 IsOpen
为真,您将跳过尝试显示它、方法 returns 和 then 已取消任务的继续执行,隐藏弹出窗口。
请注意,这里的重要部分是延续发生在方法 return 之后。即使您尝试无条件地显示弹出窗口(即在执行该部分之前不检查 IsOpen
),弹出窗口也会显示然后立即隐藏。
您可以通过使用不同的计时机制(即您可以延长计时器而不必取消和重新启动,例如 System.Threading.Timer
)或使用您现在拥有的机制但保持每次触发无效状态时递增的计数器,每次计时器的等待完成时递减,并且仅当计数器再次达到零时才隐藏弹出窗口。
但实际上,我认为这都不是很好的用户交互。作为用户,我真的开始讨厌定时工具提示,因为它们永远不会显示足够长的时间。 WPF 中一种更常见的技术是使用一种内置验证机制(WPF 中至少有三种不同的验证变体,each with their own pros and cons),然后将元素的常规工具提示值设置为您的错误消息,以便用户只要将鼠标悬停在元素上就可以看到它。
任何类型的用户输入验证都存在障碍和麻烦。但是,如果您使用这些标准之一,至少您不必处理这个计时问题,并且您的用户可能会发现它也更适合他们。 :)