Skip to content

[browser][MT] InvokeAsync in Blazor WASM components calls into the caller thread instead of the UI thread. #95547

@LostBeard

Description

@LostBeard

Description

ComponentBase.InvokeAsync does not call on the UI thread when called from another thread.

Reproduction Steps

Test project repo: Threads Test

The project msut have <WasmEnableThreads>true</WasmEnableThreads> and reference Microsoft.NET.WebAssembly.Threading. I also have the workload wasm-experimental installed.

In a Blazor WASM threading project replace the Counter.razor page with the below code.

@page "/counter"

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current text: @screenData</p>
<button class="btn btn-primary" @onclick="ButtonClicked">Use InvokeAsync</button>

@code {
    private string screenData = "";

    // This does not work using InvokeAsync
    private async void ButtonClicked()
    {
        Console.WriteLine("Message1,MyThreadID:" + System.Threading.Thread.CurrentThread.ManagedThreadId);
        screenData = "AfterMessage1";
        StateHasChanged();
        await Task.Run(async () =>
        {
            Console.WriteLine("Message2,MyThreadID:" + System.Threading.Thread.CurrentThread.ManagedThreadId);
            await Task.Delay(2000);
            Console.WriteLine("Message3,MyThreadID:" + System.Threading.Thread.CurrentThread.ManagedThreadId);
            await InvokeAsync(() =>
            {
                // This is not called on the UI thread as expected
                screenData = "AfterMessage3";
                Console.WriteLine("Message4,MyThreadID:" + System.Threading.Thread.CurrentThread.ManagedThreadId);
                // *** Below line throws errors viewable in devtools console ***
                StateHasChanged();
            });
            await Task.Delay(2000);
            Console.WriteLine("Message5,MyThreadID:" + System.Threading.Thread.CurrentThread.ManagedThreadId);
        });
        Console.WriteLine("Message6,MyThreadID:" + System.Threading.Thread.CurrentThread.ManagedThreadId);
        screenData = "AfterMessage6";
        StateHasChanged();
    }
}

Expected behavior

I expect InvokeAsync (in a Blazor WASM component) to call on the UI thread context.

Actual behavior

InvokeAsync (in a Blazor WASM component) calls on the same thread it is called from.

Regression?

Unknown.

Known Workarounds

This (InvokeAsyncAlt) works in place on InvokeAsync in a Blazor Component

    SynchronizationContext sctx = SynchronizationContext.Current!;
    Task InvokeAsyncAlt(Action action)
    {
        sctx.Post((object state) => action(), null);
        return Task.CompletedTask;
    }
    Task InvokeAsyncAlt(Func<Task> action)
    {
        sctx.Post(async (object state) => await action(), null);
        return Task.CompletedTask;
    }

Configuration

Blazor WebAssembly Standalone template with .Net 8.0.0
Windows 10 x64
.Net 8.0.100
Tested in Chrome 119.0.6045.200 and Firefox 120.0

Other information

I came across this issue while helping answer a question on StackOverflow How do child threads talk to the main (UI) thread in blazor webassembly (multithreading mode)

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions