DumbestGuyOnSaturn
DumbestGuyOnSaturn

Reputation: 51

Serialization of Access to UI Thread in MAUI

I am using the following code to change the text on a label outside of the UI thread.

The text on the label was not what I expected. It took some time to realize what was happening.

string mystringvar = null;
mystringvar = "WHAT I WANT";
Application.Current.Dispatcher.Dispatch(() =>
{
    mylabel.Text = mystringvar;
});
mystringvar = "WHAT I GOT";
// on the UI, the text on mylabel is WHAT I GOT

Is there any mechanism for determining the UI is updated before I use mystringvar again? I have not experimented, but it would make sense that this also applies to other properties of the label.

Upvotes: 0

Views: 614

Answers (2)

Stephen Quan
Stephen Quan

Reputation: 25871

The problem is your thread completes before the UI thread updates the label hence why you see the final string value not the intermediate value.

For these types of UI synchronization issues, the easiest solution is to use async/await. If you think about it, your thread pauses whilst the UI thread updates the label, when done, your thread resumes.

// MainPage.xaml.cs ...
public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
    }

    private async void OnChangeTextClicked(object sender, EventArgs e)
    {
        string mystringvar = null;
        mystringvar = "WHAT I WANT";
        Debug.WriteLine($"Step 1: MyThread: {Thread.CurrentThread.ManagedThreadId}"); // Step 1: MyThread: 10
        await MainThread.InvokeOnMainThreadAsync(() =>
        {
            Debug.WriteLine($"Step 2: MainThread: {Thread.CurrentThread.ManagedThreadId}"); // Step 2: MainThread: 1
            mylabel.Text = mystringvar + " (Step 2)";
        });
        await Task.Delay(500);
        await MainThread.InvokeOnMainThreadAsync(() =>
        {
            Debug.WriteLine($"Step 3: MainThread: {Thread.CurrentThread.ManagedThreadId}"); // Step 3: MainThread: 1
            mylabel.Text = mystringvar + " (Step 3)"; ;
        });
        await Task.Delay(500);
        await MainThread.InvokeOnMainThreadAsync(() =>
        {
            Debug.WriteLine($"Step 4: MainThread: {Thread.CurrentThread.ManagedThreadId}"); // Step 4: MainThread: 1
            mylabel.Text = mystringvar + " (Step 4)"; ;
        });
        await Task.Delay(500);
        await MainThread.InvokeOnMainThreadAsync(() =>
        {
            Debug.WriteLine($"Step 5: MainThread: {Thread.CurrentThread.ManagedThreadId}"); // Step 5: MainThread: 1
            mylabel.Text = mystringvar;
        });
        Debug.WriteLine($"Step 6: MyThread: {Thread.CurrentThread.ManagedThreadId}"); // Step 6: MyThread: 10
        mystringvar = "WHAT I GOT (NOT!)";
    }
}
<!-- MainPage.xaml ... -->
<VerticalStackLayout>
    <Label x:Name="mylabel" Text="Hello, World!" />
    <Button Text="Change Text" Clicked="OnChangeTextClicked" />
</VerticalStackLayout>

A full version of the above code is in: https://github.com/stephenquan/StackOverflow.Maui/tree/main/StackOverflow.Maui.SyncAccess

ChangeText.gif

Alternatively, you could always take a copy and ensure that the copy is never overwritten, e.g.

string mystringvar = null;
mystringvar = "WHAT I WANT";
{
    string mystringvar2 = mystringvar;
    Application.Current.Dispatcher.Dispatch(() =>
    {
        mylabel.Text = mystringvar2;
    });
}
mystringvar = "WHAT I GOT (NOT!)";

Upvotes: 1

DumbestGuyOnSaturn
DumbestGuyOnSaturn

Reputation: 51

Either I’m missing something or the technique cited by Stephen Quan above does not work, at least not for methods on the main page.

For example, in the following code, in mymainpage code (not shown) I’ve inserted a break point at “MethodOnMainPage”. When I execute the following code, which is not on the main thread, all three invocations of mymainpage.InvokeOnMainThread are executed with no break point encountered.

await MainThread.InvokeOnMainThreadAsync(() =>
{
    mymainpage.MethodOnMainPage(argument1);
});
await MainThread.InvokeOnMainThreadAsync(() =>
{
    mymainpage.MethodOnMainPage(argument2);
});
await MainThread.InvokeOnMainThreadAsync(() =>
{
    mymainpage.MethodOnMainPage(argument3);
});

Then later, after the above code completes, the break point on the main thread is activated three times in succession. This isn’t what I would expect if the invoking thread pauses whilst the UI updates.

Upvotes: 0

Related Questions