-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[browser][MT] InvokeAsync in Blazor WASM components calls into the caller thread instead of the UI thread. #95547
Comments
Tagging subscribers to this area: @mangod9 Issue DetailsDescriptionComponentBase.InvokeAsync does not call on the UI thread when called from another thread. Reproduction StepsTest project repo: Threads Test The project msut have In a Blazor WASM threading project replace the Counter.razor page with the below code. @page "/counter"
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<div>
Open devtools to see the output and errors if any. Using the InvokeAsync option may cause the page to no longer work until reloaded.
</div>
<p role="status">Current text: @screenData</p>
<button class="btn btn-primary" @onclick="ButtonClicked">Use InvokeAsync</button>
<button class="btn btn-primary" @onclick="ButtonClickedAlt">Use InvokeAsyncAlt</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(() =>
{
screenData = "AfterMessage3";
Console.WriteLine("Message4,MyThreadID:" + System.Threading.Thread.CurrentThread.ManagedThreadId);
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();
}
// This works using InvokeAsyncAlt
private async void ButtonClickedAlt()
{
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 InvokeAsyncAlt(() =>
{
screenData = "AfterMessage3";
Console.WriteLine("Message4,MyThreadID:" + System.Threading.Thread.CurrentThread.ManagedThreadId);
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();
}
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;
}
} Expected behaviorI expect InvokeAsync (in a Blazor WASM component) to call on the UI thread context. Actual behaviorInvokeAsync (in a Blazor WASM component) calls on the same thread it is called from. Regression?Unknown. Known WorkaroundsThis (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;
} ConfigurationBlazor WebAssembly Standalone template with .Net 8.0.0 Other informationI 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)
|
This is similar to how other UI apps are behaving in .Net I believe. Using Task.Run forwards the execution to the threadpool (or well, an analogue of it) where synchronization context is usually null. I personally solve this by introducing some kind of "UITask" which simply checks if current sync context is the one task expects and if it is, it runs the code right away or does post to sync context. You can also introduce some kind of awaitable that will do the similar thing like JumpToUiThreadAwaitable Another question is whether behavior you expect should be on InvokeAsync by default (e.g. auto UI thread switching) |
Tagging subscribers to 'arch-wasm': @lewing Issue DetailsDescriptionComponentBase.InvokeAsync does not call on the UI thread when called from another thread. Reproduction StepsTest project repo: Threads Test The project msut have 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 behaviorI expect InvokeAsync (in a Blazor WASM component) to call on the UI thread context. Actual behaviorInvokeAsync (in a Blazor WASM component) calls on the same thread it is called from. Regression?Unknown. Known WorkaroundsThis (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;
} ConfigurationBlazor WebAssembly Standalone template with .Net 8.0.0 Other informationI 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)
|
/cc @pavelsavara |
This is known gap. We didn't finish solving this problem on time to be included in Net8. |
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 workloadwasm-experimental
installed.In a Blazor WASM threading project replace the Counter.razor page with the below code.
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
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)
The text was updated successfully, but these errors were encountered: