WPF MVVM UI如果线程阻塞,则不会更新

361*_*615 0 c# wpf multithreading mvvm

我有一个窗口:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication1"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
  <StackPanel Orientation="Horizontal" Background="{Binding BackgroundColor}">
    <Button Content="Button1" Click="ButtonBase_OnClick"/>
  </StackPanel>
</Window>
Run Code Online (Sandbox Code Playgroud)

而这个CodeBehind窗口:

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using WpfApplication1.Annotations;

namespace WpfApplication1
{
    /// <summary>
    ///     Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private Brush _backgroundColor;

        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;
            Background = Brushes.Orange;
        }

        public Brush BackgroundColor
        {
            get { return _backgroundColor; }
            set
            {
                _backgroundColor = value;
                OnPropertyChanged();
            }
        }

        private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
        {
            Background = Brushes.Yellow;
            Thread.Sleep(5000);
        }

        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

所以基本上我单击一个按钮并更改背景颜色.除非我阻止UI线程,否则一切正常.

当我做某些事情,Background = Brushes.Yellow;Thread.Sleep(5000);我希望背景颜色改变,然后UI冻结.

但是目前UI似乎无法在冻结之前重新渲染自身,并且在Sleep()释放锁定后颜色会发生变化.

我试图使用Dispatcher,设置优先级,但行为似乎是相同的.

任何人都有想法如何设置Sleep()较低的优先级,所以UI会在进入睡眠状态之前完全更新?

PS给定的代码只是我真实情况的快速再现,我从WPF应用程序启动另一个WPF应用程序进程.

Ber*_*sch 5

简短的回答是不要在UI线程中阻止.永远.

说起来容易做起来难,但是您可以使用一些工具:

仍然有效的老派方法是使用a DispatcherTimer.DispatcherTimer在后台延迟,当计时器关闭时,您将在UI线程中获得回调.进行更新非常方便.不幸的是,围绕这个的代码有点难看.

目前最好的做法是使用asyncawait将其用.NET 4.5引入的.你的回调看起来会改变这种方式:

    private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        Background = Brushes.Yellow;
        await Task.Delay(TimeSpan.FromSeconds(5));
        Background = Brushes.Red;
    }
Run Code Online (Sandbox Code Playgroud)

我添加了第二个背景更改,因此您知道它实际上等待而不会阻止您的应用.关于这种方法有几点需要注意:

  • 你应该只void用于事件处理程序,因为那些是无法等待的.
  • 默认的返回类型为TaskTask<ReturnType>允许您以等待该方法以完成为好.

await从UI线程调用时,在系统完成等待时返回UI线程.如果您希望在从处理程序返回完成之前更新UI,则需要使用await Dispatcher.Yield().例如:

    private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        IsProcessing = true;
        await Dispatcher.Yield();

        // the UI is now showing that processing is going on
        Message = await DoProcessing();
        // update the other properties with the results

        IsProcessing = false;
    }

    private async Task<string> DoProcessing()
    {
       // Simulate heavy lifting here:
       await Task.Delay(TimeSpan.FromSeconds(5));

       return "Done processing";
    }
Run Code Online (Sandbox Code Playgroud)

通过这种变化,您可以看到即使我们定义DoProcessing()为返回a Task<string>,我们也不会直接处理任务.这是由编译器为我们完成的.该async关键字包装在你任务的返回值.当await调用准备好而不阻塞线程时,调用将解除结果.这是使用UI的一种非常方便的方式.


注意:原始问题对Thread.Sleep()行为感到困惑.该方法阻止正在运行的线程,直到完成所请求的毫秒数.如果明确调用Wait()任务对象,情况也是如此.该await关键字只是把书签中的代码,这样,当后台任务最终完成,我们可以拿起我们离开的地方.

Thread.Sleep()是一个阻塞调用,所以它会阻止它Dispatcher做任何事情,直到它完成.线程优先级是什么并不重要.