如果线程阻塞

时间:2016-05-17 13:53:40

标签: 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>

这个窗口的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));
        }
    }
}

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

当我执行Background = Brushes.Yellow;Thread.Sleep(5000);之类的操作时,我希望背景颜色发生变化,然后UI会冻结。

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

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

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

P.S。给定的代码只是我真实案例的快速再现,我从WPF应用程序开始另一个WPF应用程序进程。

2 个答案:

答案 0 :(得分:5)

简短的回答是禁止在UI线程中阻止。如初。

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

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

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

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

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

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

当您从UI线程调用await时,在系统完成等待时返回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";
    }

通过此变体,您可以看到即使我们将DoProcessing()定义为返回Task<string>,我们也绝不会直接处理该任务。这是编译器为我们完成的。 async关键字为您包装任务中的返回值。 await调用在没有阻塞线程的情况下准备就绪后会打开结果。这是使用UI的一种非常方便的方式。

注意:原始问题与Thread.Sleep()行为相混淆。该方法阻止正在运行的线程,直到完成所请求的毫秒数。如果在任务对象上显式调用Wait(),情况也是如此。 await关键字只是在代码中放置一个书签,这样当后台任务最终完成时,我们可以从中断的地方继续。

Thread.Sleep()是一个阻止调用,因此它会阻止Dispatcher在完成之前执行任何操作。线程优先级是什么并不重要。

答案 1 :(得分:2)

当UI线程空闲时,通过直接在阻止UI线程的处理程序中执行sleep来进行渲染。使用a DispatcherTimer or dispatcher frames您可以实现此目标。