Paul Claessen
Paul Claessen

Reputation: 119

A WinUI 3 question about accessing the UI thread from another thread

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>

Upvotes: 9

Views: 4625

Answers (1)

Andrew KeepCoding
Andrew KeepCoding

Reputation: 13203

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.

Upvotes: 12

Related Questions