Prevent Using Dispatcher.Invoke in Wpf Code

Prevent using Dispatcher.Invoke in WPF code

Regarding this:

This works, but this is not the only event and thus makes my code
horrible ugly

Yes, your WPF-based code will definitely be extremely horrible unless you understand and embrace The WPF Mentality.

Basically, all interactions between your custom logic (AKA Business logic or Application Logic) and the WPF UI should manifest in the form of Declarative DataBinding as opposed to the traditional imperative approach.

This means that there should be nothing like this:

UserWindow.Visibility = Visibility.Hidden;

anywhere in your code, simply because introducing things like that makes your code dependent on the UI and thus only executable on the UI thread.

Instead, the WPF approach to that would be to declaratively DataBind the Visibility propety of the UI element (IN XAML) to a relevant bool property that you can operate from the outside, like this:

<UserWindow Visibility="{Binding ShowUserWindow, Converter={my:BoolToVisibilityConverter}}">
<!-- ... -->
</UserWindow>

Then, you would need to create a relevant class that contains the properties the UI is expecting to bind to. This is called a ViewModel.

Notice that in order to properly support Two-Way WPF DataBinding, your ViewModels must Implement the INotifyPropertyChanged interface.

When doing so, it is also convenient to have the PropertyChanged event from that interface marshalled to the UI thread, so that you no longer have to worry about setting the ViewModel's properties by using the Dispatcher.

Therefore our first step is to have all our ViewModels inherit from a class like this:

(taken from this answer):

public class PropertyChangedBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

protected virtual void OnPropertyChanged(string propertyName)
{
//Raise the PropertyChanged event on the UI Thread, with the relevant propertyName parameter:
Application.Current.Dispatcher.BeginInvoke((Action) (() =>
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}));
}
}

Once we have our Property Change Notification Dispatch to the UI Thread in place, we can proceed to create a relevant ViewModel that suits, in this case, the UserWindow and it's DataBinding expectations:

public class UserViewModel: PropertyChangedBase
{
private bool _showUserWindow;
public bool ShowUserWindow
{
get {return _showUserWindow; }
set
{
_showUserWindow = value;
OnPropertyChanged("ShowUserWindow"); //This is important!!!
}
}
}

Finally, you would need to set the Window's DataContext to an instance of it's corresponding ViewModel. One simple way to do that is in the Window's constructor:

public UserWindow() //Window's Constructor
{
InitializeComponent(); //this is required.

DataContext = new UserViewModel(); //here we set the DataContext
}

As you can see in this example, there is literally no need to manipulate the UI element's properties in procedural code. This is good not only because it resolves the Thread Affinity issues (because now you can set the ShowUserWindow property from any thread), but also because it makes your ViewModels and logic completely decoupled from the UI and thus testable and more scalable.

This same concept applies to EVERYTHING in WPF.

One detail that I need to mention is that I'm making use of a technique of Combining MarkupExtension and IValueConverter in order to reduce the the XAML boilerplate involved in using Converters.

You can read more about that in the link and also the MSDN DataBinding page linked above.

Let me know if you need further details.

Inconsistent dispatcher.Invoke behavior

It sounds like you're writing a kiosk application (ie. full-screen, non-interactive). If this is the case I think you would be better off having a single window and switching the views inside it, rather than switching between two separate windows. Also, you need to separate the database query work from the refreshing of the window content. Furthermore, I think it would help if the views knew nothing about each other: at the moment your first window is tightly coupled to your second, which is not really a good idea.

In my opinion, if you changed your architecture a little, a lot of the problems you are having would disappear. Here's what I would recommend:

First, just go with a single window. Create two user controls (Project > Add User Control), and move your XAML layout from your existing windows into these two new controls. Then make your main window look something like this:

<Window x:Class="StackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:StackOverflow"
WindowState="Maximized" WindowStyle="None">
<Grid>
<my:UserControl1 x:Name="_first" Panel.ZIndex="1" />
<my:UserControl2 Panel.ZIndex="0" />
</Grid>
<Window.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard AutoReverse="True" RepeatBehavior="Forever">
<ObjectAnimationUsingKeyFrames BeginTime="0:0:5" Duration="0:0:5"
Storyboard.TargetName="_first"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0"
Value="{x:Static Visibility.Hidden}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Window.Triggers>
</Window>

This is a full-screen window with no chrome that contains your two user controls (essentially the contents of your existing windows). They are layered in a Grid element so that one sits on top of the other: I'm using the Panel.ZIndex property to force the first control to the top of the pile. Finally, I'm using an animation (triggered when the window loads) that toggles the visibility of one of the controls to hide it after a certain period of time. The animation is set to repeat and auto-reverse, the effect of which is to hide one of the controls, then make it visible again. You can change the Duration attribute value to control how long each control "stays" visible; it's set to 5 seconds in this example, which means a 10 second delay between switches.

The key to this working is that the first user control, when visible, must fully obscure the other user control that lies beneath it. This is easy to accomplish by setting the background colour of the control.

Your user controls can contain anything that a window would contain. Here's the example user control XAML that I used:

<UserControl x:Class="StackOverflow.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Background="White" Padding="40">
<TextBlock Text="{Binding Number}" FontSize="60"
TextAlignment="Center" VerticalAlignment="Top" />
</UserControl>

As you can see it's just a TextBlock element whose Text property binds to a Number property defined in the user control's code-behind. I used the same XAML for both user controls, just varying the VerticalAlignment of the text so that I could tell which control was visible at any given time.

The code-behind looks like this (it's the same for both, with the exception of the class name):

using System;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Threading;

namespace StackOverflow
{
public partial class UserControl1 : UserControl, INotifyPropertyChanged
{
public UserControl1()
{
InitializeComponent();
DataContext = this;

_timer = new DispatcherTimer
{ Interval = TimeSpan.FromSeconds(5), IsEnabled = true };
_timer.Tick += (sender, e) => Task.Run(async () => await DoWorkAsync());
}

readonly DispatcherTimer _timer;
readonly Random _random = new Random();

public event PropertyChangedEventHandler PropertyChanged;

public int Number
{
get
{
return _number;
}
private set
{
if (_number != value)
{
_number = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Number"));
}
}
}
}
int _number;

async Task DoWorkAsync()
{
// Asynchronous code started on a thread pool thread

Console.WriteLine(GetType().Name + " starting work");
_timer.IsEnabled = false;
try
{
await Task.Delay(TimeSpan.FromSeconds(_random.Next(4, 12)));
Number++;
}
finally
{
_timer.IsEnabled = true;
}
Console.WriteLine(GetType().Name + " finished work");
}
}
}

It basically contains a single Number property (which implements INotifyPropertyChanged) that gets incremented by a "worker" method. The worker method is invoked by a timer: here, I'm using a DispatcherTimer, but as I'm not changing any UI elements directly any of the .NET timers would have done.

The worker is scheduled to run on the thread pool using Task.Run, and then runs asynchronously. I'm simulating a long-running job by waiting for a period of time with Task.Delay. This worker method would be where your database query gets called from. You can vary the gap between successive queries by setting the timer's Interval property. There's nothing to say that the gap between queries need be the same as the refresh interval of your UI (ie. the speed at which the two views are switched); indeed, as your query takes a variable amount of time, syncing the two would be tricky anyway.

Avoid requiring Dispatcher.BeginInvoke

Using Hans Passant's comment above, I just modified my code as follows:

    private SynchronizationContext MainUIThread; //as a class field

In the constructor:

public MyClass()
{
MainUIThread = SynchronizationContext.Current;
}

Modification to the event structure:

    public event EventHandler<MessageRecievedEventArgs> MessageRecieved;

protected virtual void OnMessageReceived(object sender, MessageRecievedEventArgs args)
{
var handle = MessageRecieved;

if (handle == null)
return;

if(MainUIThread != null)
{
MainUIThread.Post(d => handle(sender, args), this);
}
else
{
handle(sender, args);
}
}

Dispatcher.Invoke blocks forever

I eventually found a solution to my problem : I just need to show the dialog on a new thread, with its own dispatcher. Here's the modified code :

class DialogService : IDialogService
{
private readonly Dispatcher _dispatcher = Application.Current.Dispatcher;

public bool? Show(IDialogViewModel viewModel)
{
if (_dispatcher.CheckAccess())
{
DoShow(viewModel);
}
else
{
bool? r = null;
Thread thread = new Thread(() => r = DoShow(viewModel));
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
return r;
}
}

private static bool? DoShow(IDialogViewModel viewModel)
{
var dialogWindow = new DialogWindow();
return dialogWindow.Show(viewModel);
}
}

Why does WPF's Dispatcher.Invoke not cause a deadlock when run on the main thread?

I believe your misunderstanding may be based around the following thought process:

"Well, Invoke blocks the calling thread until the action is completed. How can it perform the action on the thread if the thread is blocked?"

If we look inside the source, we see that the callback is being called not only on the same thread, but directly* inside the Invoke method. The main thread is not being blocked.

If you look at the dispatcher's Reference Source page, you can see the following comment above an if statement within the Invoke method's implementation, with the callback being called within it:

// Fast-Path: if on the same thread, and invoking at Send priority,
// and the cancellation token is not already canceled, then just
// call the callback directly.
if(!cancellationToken.IsCancellationRequested && priority == DispatcherPriority.Send && CheckAccess())
{
/* snipped */

callback();

/* snipped */
}

You're calling Dispatcher.Invoke on the main thread, and the method handles that by just calling it instantly.

*Well, not directly, but the entire body of Invoke(Action) is just a call to the method that the above code is in.

Does Dispatcher.Invoke block on the calling thread?

Seems like it will block :)

Have a look here: Dispatcher.Invoke from a new thread is locking my UI

Here's some more wisdom:

Invoke is synchronous and BeginInvoke is asynchronous. The operation is added to the event queue of the Dispatcher at the specified DispatcherPriority.

WPF screen freeze use Dispatcher BeginInvoke methods

When you "invoke into a thread", you're telling that thread to stop whatever it's doing and execute your code instead.

So you're starting a thread which promptly blocks the UI thread while it does all of its work there. That wasn't your intent.

Instead of putting the entire loop in the Invoke lambda, just invoke for the one tiny little part where you need to touch the UI:

ThreadPool.QueueUserWorkItem(
o =>
{
int a = 0;
for (int i = 0; i < 10000000000; i++)
{
a = a + i;
FaturaCount.Dispatcher.Invoke(() =>
{
txtCount.Text = a.ToString();
});
}
});

Clearly, the above code doesn't do anything useful, and such rapid repeated calls to Invoke don't make much sense. That'll tie up the UI very nearly as badly as your original version. Modern hardware can increment integers and iterate loops very, very quickly. Is this your actual code, or is the loop in the thread actually doing something more useful, with longer intervals between UI updates? If I were implementing a thread that just displayed an incrementing integer in a TextBlock, I'd put a Thread.Sleep(250) or something in the loop outside the Invoke. Give the UI a chance to respond to user input.

Or, much, much better yet, since this is WPF, write a viewmodel and update txtCount.Text via a binding, which will do the invoke for you. You won't even have to think about the dispatcher then.



Related Topics



Leave a reply



Submit