1

I want to update the position of certain UI elements in my WPF application in a loop. After each iteration, the UI should be rerendered to make the changes visible. The updating process can stopped at any point using a CancellationToken. Since the cancellation is performed by the user, the UI must remain responsive to input. I wrote the following method to do this:

public async Task DoStuff(CancellationToken token)
{
    do
    {
        DoLayoutUpdate();

        await Dispatcher.Yield(DispatcherPriority.Input);
    } while (!token.IsCancellationRequested);
}

This mostly works: The UI is rerendered after each iteration and I can click the button to cancel the operation so input works as well. The problem is: if there is no input and nothing to rerender, the method gets stuck in the Yield. Presumably the thread is blocked waiting for input or render tasks.

If I increase the DispatcherPriority to Render, the method does not get stuck anymore but then the UI isn't updated and input isn't handled anymore.

How can I fix this?

Wouter
  • 538
  • 6
  • 15
  • 1
    Did you try `await Task.Delay(10);` or `await Dispatcher.BeginInvoke(new Action(() => { }), System.Windows.Threading.DispatcherPriority.Input);`? – mm8 Feb 11 '19 at 15:25
  • 1
    In other words your question is how to check if dispatcher queue empty? Dispatcher is a [PriorityQueue](https://referencesource.microsoft.com/#WindowsBase/Base/System/Windows/Threading/Dispatcher.cs,3220), that would be rather complicated. It seems you are trying to implement [DoEvents()](https://stackoverflow.com/a/11899439/1997232) which is fault technique. Rather split your job into reasonable tasks and invoke those, hard to recommend something more specific without details. You you are making a game or something - there is a [way](https://stackoverflow.com/a/16992367/1997232) – Sinatr Feb 11 '19 at 15:53
  • Why update the layout in code instead of using eg [animated transforms](https://learn.microsoft.com/en-us/dotnet/framework/wpf/graphics-multimedia/animation-overview) ? – Panagiotis Kanavos Feb 11 '19 at 15:56
  • @PanagiotisKanavos I can't use animated transforms, the layout is non-trivial and needs to be recalculated manually. – Wouter Feb 11 '19 at 16:33
  • @mm8 both of those seem to work perfectly. If you make it into an answer, I'll mark it as correct. – Wouter Feb 11 '19 at 16:34
  • @Wouter I wouldn't call path transforms or storyboards of transforms trivial in any case. That's how games are built. You can create custom transforms anyway. It's a lot better to use the built-in mechanism than try to rebuild it – Panagiotis Kanavos Feb 12 '19 at 07:30

3 Answers3

1

Try await Task.Delay(10); or await Dispatcher.BeginInvoke(new Action(() => { }), System.Windows.Threading.DispatcherPriority.Input); instead of Dispatcher.Yield.

This should give the UI thread a chance to render while your loop executes.

mm8
  • 163,881
  • 10
  • 57
  • 88
0

If I increase the DispatcherPriority to Render, the method does not get stuck anymore but then the UI isn't updated and input isn't handled anymore.

Actually, the problem is that you changed the priority in the wrong direction. Setting the priority to DispatcherPriority.Background would allow WPF to finish its work and then eventually schedule the continuation to allow the method to resume execution after the await.

I.e.:

public async Task DoStuff(CancellationToken token)
{
    do
    {
        DoLayoutUpdate();

        await Dispatcher.Yield(DispatcherPriority.Background);
    } while (!token.IsCancellationRequested);
}

Using a higher priority causes your continuation to be scheduled too soon, giving your loop all the dispatcher time, in preference over all the other things WPF needs to do.

Noting, of course, that calling Dispatcher.Yield() without a parameter will default to using DispatcherPriority.Background as well. Either way works fine.

The other ideas suggested in the accepted answer will work too, but they are a bit kludgy as compared to simply yielding with the correct requested continuation priority.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
0

Or the bizarre monstrosity that works no matter what thread you're running on:

        await Task.Run(() =>
        {
            Action action = () => { };
            MainWindow.Dispatcher.Invoke(action, 
                System.Windows.Threading.DispatcherPriority.Background);
        });

Dispatcher.Yield() works fine on the UI thread. But it is a static method that operates on Dispatcher.CurrentDispatcher, and there's no equivalent non-static member.

Robin Davies
  • 7,547
  • 1
  • 35
  • 50