1

I have a window:

<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>

And this CodeBehind for the window:

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));
        }
    }
}

So basically I click a button and change background color. Everything works fine, unless I'm blocking the UI Thread.

When I do something like Background = Brushes.Yellow;Thread.Sleep(5000); I expect the background color to change, and then UI to freeze.

But currently UI seems not to be able to re-render itself before freezing and the color is changed after Sleep() release the lock.

I've tried to play around with Dispatcher, setting the priority, but behavior seems to be the same.

Anyone got ideas how to set for Sleep() lower priority, so the UI would be updated completely before goining to sleep?

P.S. The given code is just a quick reproduction of my real case, where I'm from WPF application starting another WPF application process.

3615
  • 3,787
  • 3
  • 20
  • 35

2 Answers2

7

The short answer is Don't block in the UI thread. Ever.

It's easier said than done, however there are a few tools at your disposal:

The old school method that still works is to use a DispatcherTimer. The DispatcherTimer delays in the background and when the timer goes off, you get a callback in the UI thread. Very convenient for doing updates. Unfortunately, the code surrounding this is kind of ugly.

The current best practice is to use async and await which were introduced with .NET 4.5. The way your callback would look changes to this:

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

I added the second background change so you know it actually waited without blocking your app. There's a couple things to note about this approach:

  • you should only use void for event handlers since those can't be awaited.
  • the default return type is either Task or Task<ReturnType> which allows you to await for that method to finish as well.

When you call await from the UI thread, you return to the UI thread when the system is done awaiting. If you want the UI to update before you are done returning from the handler, then you need to use await Dispatcher.Yield(). For example:

    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";
    }

With this variation, you can see that even though we define DoProcessing() as returning a Task<string>, we never deal with the task directly. That's done by the compiler for us. The async keyword wraps the return value in a Task for you. The await call unwraps the result when it's ready without blocking the thread. This is a very convenient way of working with the UI.


NOTE: The original question was confused about Thread.Sleep() behavior. That method blocks the running thread until the requested number of milliseconds is done. The same is true if you explicitly call Wait() on a task object. The await keyword just puts a bookmark in the code so that when the background task finally completes, we can pick up where we left off.

Thread.Sleep() is a blocking call, so it will stop the Dispatcher from being able to do anything until it is done. It does not matter what the thread priority is.

Berin Loritsch
  • 11,400
  • 4
  • 30
  • 57
  • Thanks for the great answer! But isn't the solution involving Task.Delay quite cumbersome? I've found this one myself, but didn't want to go with it, because maybe depending on the machine it would be not enough X sec to update the UI? – 3615 May 17 '16 at 15:09
  • Your example was using `Thread.Sleep()`, to which `Task.Delay()` is the equivalent non-blocking call. The second example showing how I am waiting for a long running method to return demonstrates quite adequately how you can get the logic the way you want it, and it will always be called when the long running process is done. – Berin Loritsch May 17 '16 at 16:05
  • If your intent is to block the UI thread (which is bad form), then at least call `await Dispatcher.Yield()` first to allow the UI to update before blocking it. – Berin Loritsch May 17 '16 at 16:13
  • Yesterday I've made some tries with `Dispatcher.Yield()` and is seemed to work. But now after some more tries I've observed that `Dispatcher.Yield()` sometimes is able to update the UI before the UI Thread block. Is there something more, then just calling it, to make it work stable? – 3615 May 18 '16 at 06:24
  • @3615, Why are you trying to block the dispatcher thread? Just disable the controls you want disabled (`Enabled=false`) and do all your work in the background. You can even bind `Enabled` to a boolean property like I outlined in my answer. The app will work so much better when you think of it like that. – Berin Loritsch May 18 '16 at 12:34
  • Another consequence of blocking the dispatcher thread is that your app will appear as "Not Responding" in the Task Manager, which is makes it look like your application is buggy. – Berin Loritsch May 18 '16 at 12:35
  • I'm well aware of all these things, it's just the application I'm working on is made this way(not my choise), and now I'm not able to perform a refactoring to change the way it works. As I've already told, my application is spawning another applications, so It's quite Ok that it just hangs, while the work in other application is not finished. The problem is that it hangs, while progressbar is shown and everything is not grayed out. This looks ugly and I was given a task to fix this appearance, leaving the way how everything works. – 3615 May 18 '16 at 12:40
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/112299/discussion-between-berin-loritsch-and-3615). – Berin Loritsch May 18 '16 at 13:08
3

Rendering happens when the UI thread is idle, by executing the sleep directly in the handler you block the UI thread. Using a DispatcherTimer or dispatcher frames you might be able to achieve this.

Community
  • 1
  • 1
H.B.
  • 166,899
  • 29
  • 327
  • 400