尽管主线程正在运行,但 WPF GUI 未更新



我有一个带绑定的文本框:

<TextBox Text="{Binding TestString, UpdateSourceTrigger=PropertyChanged}" />

这里是 TestString 实现:

    private string testString;
    public string TestString
    {
        get { return testString; }
        set
        {
            if (testString != value)
            {
                testString = value;
                PropChanged(); // INotifyPropertyChanged Implementation ..
                System.Threading.Thread.Sleep(100);
            }
        }
    }

TestString 属性的 set 方法需要几毫秒(例如,您可以使用 100 毫秒的睡眠来测试它),然后如果您按住字母键,您首先会看到带有几个字母的编辑更新,然后 GUI 停止更新/刷新您按住字母键的所有时间。如果释放密钥,GUI 将继续更新。

我知道对于长时间运行的操作,我应该使用异步编程机制,但烦人的是,我不完全知道 WPF 开始不再更新时的阈值是多少。我个人不想为持续 5 毫秒、10 毫秒或 100 毫秒的短操作实现异步开销,因为 UI 仍然响应足够(如果 WPF 会更新......

有谁知道为什么会发生这种情况,或者如何在不使用异步编程的情况下解决此问题?

在Borland VCL(我猜在WinForms中),这根本不是问题,因为GUI在每个关键事件之后都会更新。

顺便说一下,如果使用滑块,则 UI 鼠标移动事件比保持键多得多,如果使用滑块,WPF 更新 GUI 没有问题,即使 UI 线程中的操作持续很长时间(唯一的问题是 UI 的反应更慢)。

不要在你的setter中放置任何同步逻辑。通常,我根本不建议将逻辑放入您的 setter 中,但是当您这样做时 - 异步执行。否则,您的 UI 将如您所描述的那样被阻止。

 private string _testString;
public string TestString
{
    get { return _testString; }
    set
    {
        // Custom Method, returns true if property has changed
        if (SetProperty(ref _testString, value))
        {
            DoSomeStuff();
        }
    }
}
private async void DoSomeStuff()
{
    // Do long lasting calls here
    await Task.Delay(3000);
}

wpf中的主线程(UI线程)基本上没有其他无限循环。循环基本上是这样做的:

  1. 处理所有事件;2. 渲染用户界面;1. 处理所有事件;2. 渲染 UI 等。

因此,当您按键时,它会触发一个事件并更改复选框中的文本。然后,wpf 数据绑定更改 TestStrin g 属性,并在完成所有操作后呈现 UI

现在,循环重复。第一步是处理所有事件。但是由于前一个周期花费的时间更长,因此需要处理很多事件(事件在某处缓冲)。在 UI 呈现之前,多个事件会多次更改 TestString 属性。因此,这个周期比第一个周期占用的事件更多,下一个周期将不得不处理更多的事件,并且您的 UI 线程充斥着事件处理程序。

那么在一个循环周期中有多少时间呢?好吧,错误的问题。除其他外,这取决于客户的PC设置以及他的键盘速度(当您按住该键时,该键重复的频率)。

您应该以这样的方式编写代码,即使事件被多次触发,您也不会用事件处理淹没 UI 线程。

编辑:您可能对 Binding.Delay 属性感兴趣。例如,延迟 100 毫秒告诉 WPF 每秒最多更新 10 次绑定源(在本例中为 TestString),如果TextBox.Text更改了 25 次,则事件。

另一种解决方案是Reactive Extensions库。它基本上可以帮助您使用 linq 之类的运算符来处理事件。例如:

Observable.FromEvent<PropertyChangedEventArs)(this, nameof(PropertyChanged))
  .Where(e => e.PropertyName == nameof(TestString))
  .Sample(TimeSpan.FromMiliseconds(100))
  .Subscribe(() => Thread.Sleep(...))

最新更新