A WinUI 3 question about accessing the UI thread from another thread
Asked Answered
I

1

8

A WinUI 3 question about accessing the UI thread from another thread, say, a Timer tick.

In WinForms that was extremely easy with a (someControl).InvokeRequired and .Invoke. In WPF we had to add .Dispatcher. and in UWP we had to specify a specific dispatcher: Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(...). But it still all worked just fine.

When I port my UWP solution (an extremely simple program to just show the issue), and adjust the name of the class (and thus its constructor), it still builds fine, but when I run it, I get an "a method was called at an unexpected time" exception. (Again: works fine as a UWP app)

I'll include my test code below:

Any idea what's happening here? HOW do I fix this, and, bonus answer: why does this happen in WinUI 3 and not in UWP?

(And yes, I'm aware of DispatcherTimer, I use Timer to demonstrate my problem: in my actual app I use something far more complicated than a timer, and I wanted to keep the demo code as concise and short as possible).

Demo code:

using Microsoft.UI.Xaml;
using System;
using System.Threading;
using Windows.UI.Core;

namespace WinUI3NotOwner
{
    public sealed partial class MainWindow : Window
    {
        public Timer timer;
        public int counter = 0;

        public MainWindow()
        {
            this.InitializeComponent();
            timer = new Timer(TimerTick, null, 1000, 1000);
        }

        public void TimerTick(Object stateInfo)
        {
            if (++counter == 3)
            {
                Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
                () => { TbTest.Text = "Works!"; }); // WinUI 3 code. Ignore the warning: we actually DON'T want to wait for its completion!
            }
        }
    }
}

/* XAML code used:
<Window
    x:Class="WinUI3NotOwner.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:WinUI3NotOwner"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid>
        <TextBox x:Name="TbTest" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="50,50,0,0" TextWrapping="Wrap" Text="TextBox" Width="200" Height="40"/>
    </Grid>

</Window>

*/

Iamb answered 24/8, 2022 at 0:41 Comment(0)
M
11

You should use the DispatcherQueue.

public void TimerTick(Object stateInfo)
{
    if (++counter == 3)
    {
        //Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
        //() => { TbTest.Text = "Works!"; }); // WinUI 3 code. Ignore the warning: we actually DON'T want to wait for its completion!
        DispatcherQueue.TryEnqueue(() => { TbTest.Text = "Works!"; });
    }
}

Your code doesn't work in WinUI 3 because it doesn't support desktop apps. You might find this doc helpful.

Morel answered 24/8, 2022 at 1:9 Comment(3)
THANK you! That indeed did it! I read the document you mentioned: my head is still spinning, will have to re-read it a few more times. ;-) But what really gets me is this "WinUI 3 [because it] doesn't support desktop apps" Am I really the last person on Earth to use a desktop ... and an app running on it? ;-) But again: thanks! At least my MIDI ap now works! Much obliged!Iamb
I created a video about the DispatcherQueue.Morel
This was great, thank you so much.Robet

© 2022 - 2024 — McMap. All rights reserved.