ToolmakerSteve
ToolmakerSteve

Reputation: 21341

Maui's MainThread class lacks DispatchDelayed method. How do the equivalent?

For many Maui apps, class Mainthread has convenient methods to run code on the main UI thread, from code that may be running in a background thread.

IMPORTANT: See caveats.

However, MainThread lacks the equivalent of method IDispatch.DispatchDelayed.

How do DispatchDelayed in Maui (.Net 8 or above)?

Upvotes: 2

Views: 210

Answers (1)

ToolmakerSteve
ToolmakerSteve

Reputation: 21341

  1. If this code is in code-behind of some UI object, instead of MainThread, use the UI object's Dispatcher:
public partial class MainPage : ContentPage
{
    ...

    public void SomeMethod()
    {
        // "this." added for clarity. Can be omitted.
        this.Dispatcher.DispatchDelayed(TimeSpan.FromMilliseconds(100), () =>
        {
            // Your code here.
        });
    }
}

  1. If this code is in a non-UI class, the equivalent of MainThread is Application.Current.Dispatcher:
        Application.Current.Dispatcher.DispatchDelayed(TimeSpan.FromMilliseconds(100), () =>
        {
            // Your code here.
        });

As noted in the "See caveats" link in question, if an app opens more than one window (on a desktop OS), both MainThread and Application.Current.Dispatcher refer to the UI thread of the first window opened by the app.

To dispatch on a second window's UI thread, from non-UI-code, it is necessary to pass the window's Dispatcher to that code, so that DispatchDelayed can be called on it. Something like:

    // Code in some UI code-behind class:
    SomeModel someModel = ...;
    someModel.MyMethod(this.Dispatcher, ...);
    // code in "class SomeModel"
    public void MyMethod(IDispatcher dispatcher, ...)
    {
        dispatcher.DispatchDelayed(...);
    }

  1. It is also possible to manually write code equivalent to DispatchDelayed, using MainThread:
        MainThread.BeginInvokeOnMainThread(async () => {
            await Task.Delay(100);   // Time in milliseconds.
            // Your code here
        });

An Example

I ran into a case in UI code behind, where there was a bug because I invoked code to run on UI thread, BUT that code needed to NOT RUN until the current UI method finished.

MainThread.BeginInvokeOnMainThread runs code IMMEDIATELY, if already on the MainThread! It runs BEFORE returning from the current UI method.

This code was bugged (ran too early):

        MainThread.BeginInvokeOnMainThread(async () => {
            // My code here -- it ran before the containing method returned!
        });

This code fixes it:

        MainThread.BeginInvokeOnMainThread(async () => {
            await Task.Delay(1);   // A tiny delay, to queue the following code.
            // My code here
        });

I didn't want to have to write a separate line for the delay. The code was in a separate class. I did not want to bother passing around a dispatcher. I used this solution:

        Application.Current.Dispatcher.DispatchDelayed(TimeSpan.FromMilliseconds(1), () =>
        {
            // My code here.
        });

I decided it was worth having convenience methods. Added these to my App class:

    public partial class App : Application
    {
        public static App It => (Application.Current as App);

        /// <summary> Dispatches on the Dispatcher associated with first Window created; same as MainThread. </summary>
        public static bool DispatchDelayed(TimeSpan timeSpan, Action action)
            => It.Dispatcher.DispatchDelayed(timeSpan, action);

        /// <summary> Small delay to force "action" to be queued, even if already on MainThread.
        /// This is useful if inside a method that needs to return, even when already on MainThread, before running "action".
        /// </summary>
        public static void QueueOnMainThread(Action action)
            => It.Dispatcher.DispatchDelayed(TimeSpan.FromMilliseconds(1), action);

Usage:

    App.QueueOnMainThread(() =>
    {
        // My code here.
    });

Alternate Usage, if need to call an async method:

    App.QueueOnMainThread(async () =>
    {
        await SomeMethod(...);
    });

Where SomeMethod is defined:

public async Task SomeMethod(...)
{
}

Upvotes: 3

Related Questions