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

时间:2016-01-20 13:55:48

标签: c# wpf user-interface

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

<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方法需要几毫秒(例如,你可以用100ms的Sleep来测试它),然后如果按住一个字母键,你首先会看到带有几个字母的编辑更新然后GUI会在您按住一个字母键时停止更新/刷新。如果密钥被释放,gui会继续更新。

我知道对于长时间运行的操作,我应该使用异步编程机制,但令人讨厌的是,我不知道什么是WPF开始不再更新时的阈值。 我个人不希望为持续5或10或100ms的短动作实现异步开销,因为UI仍然足够响应(如果WPF会更新......)。

有没有人知道为什么会这样,或者我如何在不使用异步编程的情况下解决这个问题?

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

顺便说一句,如果你使用滑块,那么除了持有键之外,还有更多的UI mousemove事件,如果使用滑块,WPF在更新GUI时没有问题,即使ui线程中的操作持续很长时间(唯一的问题是ui反应更慢。)

2 个答案:

答案 0 :(得分:1)

不要在你的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);
}

答案 1 :(得分:0)

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

  1. 处理所有事件;渲染ui;处理所有事件; 2.渲染ui等..
  2. 因此,当您按下键时,它会触发一个事件并更改复选框中的文本。 wpf数据绑定然后更改TestStrin g属性,并且在完成所有操作后,将呈现UI

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

    那么你在一个循环循环中有多少时间?错误的问题。其中,它取决于客户端的PC设置以及他的键盘有多快(当你按住它时,键的重复频率是多少)。

    您应该以这样的方式编写代码,即使事件多次触发,您也不会在事件处理过程中泛滥UI线程。

    修改 您可能对Binding.Delay财产感兴趣。例如,延迟100ms告诉WPF最多每秒10次更新绑定源(在本例中为TestString),如果TextBox.Text已更改25次,则为事件。

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

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