Skip to content

Commit

Permalink
feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelsavara committed Jan 3, 2024
1 parent 6ff26e4 commit a764b18
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 53 deletions.
9 changes: 3 additions & 6 deletions src/Components/Components/src/RenderTree/Renderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -711,13 +711,10 @@ private ComponentState GetRequiredRootComponentState(int componentId)
/// </summary>
protected virtual void ProcessPendingRender()
{
lock (_lockObject)
if (_rendererIsDisposed)
{
if (_rendererIsDisposed)
{
// Once we're disposed, we'll disregard further attempts to render anything
return;
}
// Once we're disposed, we'll disregard further attempts to render anything
return;
}

ProcessRenderQueue();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Infrastructure;
using Microsoft.AspNetCore.Components.WebAssembly.Rendering;
using Microsoft.AspNetCore.Components.WebAssembly.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Json;
Expand Down Expand Up @@ -75,6 +76,8 @@ internal WebAssemblyHostBuilder(
Services = new ServiceCollection();
Logging = new LoggingBuilder(Services);

InitializeWebAssemblyRenderer();

// Retrieve required attributes from JSRuntimeInvoker
InitializeNavigationManager(jsMethods);
InitializeRegisteredRootComponents(jsMethods);
Expand Down Expand Up @@ -176,6 +179,14 @@ private WebAssemblyHostEnvironment InitializeEnvironment(IInternalJSImportMethod
return hostEnvironment;
}

private static void InitializeWebAssemblyRenderer()
{
// capture the JSSynchronizationContext from the main thread, which runtime already installed.
// if SynchronizationContext.Current is null, it means we are on the single-threaded runtime
// if user somehow installed SynchronizationContext different from JSSynchronizationContext, they need to make sure the behavior is consistent with JSSynchronizationContext.
WebAssemblyRenderer._mainSynchronizationContext = SynchronizationContext.Current;
}

/// <summary>
/// Gets an <see cref="WebAssemblyHostConfiguration"/> that can be used to customize the application's
/// configuration sources and read configuration attributes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,17 @@ internal sealed partial class WebAssemblyRenderer : WebRenderer
{
private readonly ILogger _logger;
private readonly Dispatcher _dispatcher;
internal static SynchronizationContext? _mainSynchronizationContext;

public WebAssemblyRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, JSComponentInterop jsComponentInterop)
: base(serviceProvider, loggerFactory, DefaultWebAssemblyJSRuntime.Instance.ReadJsonSerializerOptions(), jsComponentInterop)
{
_logger = loggerFactory.CreateLogger<WebAssemblyRenderer>();

// if SynchronizationContext.Current is null, it means we are on the single-threaded runtime
// otherwise capture the JSSynchronizationContext from the main thread, which runtime already installed.
// if user installed SynchronizationContext different from JSSynchronizationContext, they need to make sure the behavior is consistent with JSSynchronizationContext.
var currentCtx = SynchronizationContext.Current;
_dispatcher = currentCtx == null
_dispatcher = _mainSynchronizationContext == null
? NullDispatcher.Instance
: new WebAssemblyDispatcher(currentCtx);
: new WebAssemblyDispatcher(_mainSynchronizationContext);

ElementReferenceContext = DefaultWebAssemblyJSRuntime.Instance.ElementReferenceContext;
DefaultWebAssemblyJSRuntime.Instance.OnUpdateRootComponents += OnUpdateRootComponents;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@page "/counter"
@using System.Runtime.InteropServices
@using System.Threading

<h1>Counter</h1>

Expand All @@ -9,7 +10,7 @@

@code {
int currentCount = 0;
Timer timer;
System.Threading.Timer timer;

void IncrementCount()
{
Expand All @@ -18,7 +19,7 @@

protected override async Task OnInitializedAsync()
{
if(!OperatingSystem.IsBrowser())
if (!OperatingSystem.IsBrowser())
{
return;
}
Expand All @@ -29,52 +30,69 @@
}

Exception exc = null;
try
{
// send me to the thread pool
await Task.Delay(10).ConfigureAwait(false);
StateHasChanged(); // render should throw
}
catch(Exception ex)

// run in the thread pool
await Task.Run(() =>
{
exc=ex;
Console.WriteLine(ex.Message);
}
try
{
StateHasChanged(); // render should throw
return Task.CompletedTask;
}
catch (Exception ex)
{
Console.WriteLine("After expected fail " + Environment.CurrentManagedThreadId);
exc = ex;
return Task.CompletedTask;
}
});

if (exc == null || exc.Message == null || !exc.Message.Contains("The current thread is not associated with the Dispatcher"))
{
throw new Exception("We should have thrown here!");
}

// test that we could create new thread
var tcs = new TaskCompletionSource<int>();
var t = new Thread(() => {
var t = new Thread(() =>
{
Console.WriteLine("From new thread " + Environment.CurrentManagedThreadId);
tcs.SetResult(Thread.CurrentThread.ManagedThreadId);
});
t.Start();
var newThreadId = await tcs.Task;
if (newThreadId == 1){
if (newThreadId == 1)
{
throw new Exception("We should be on new thread in the callback!");
}

timer = new Timer(async (state) =>
timer = new System.Threading.Timer(async (state) =>
{
// send me to the thread pool
await Task.Delay(10).ConfigureAwait(false);
if (Thread.CurrentThread.ManagedThreadId == 1)
{
throw new Exception("We should be on thread pool thread!");
}
Console.WriteLine("From timer " + Environment.CurrentManagedThreadId);

await InvokeAsync(() =>
// run in the thread pool
await Task.Run(async () =>
{
if (Thread.CurrentThread.ManagedThreadId != 1)
if (Thread.CurrentThread.ManagedThreadId == 1)
{
throw new Exception("We should be on main thread again!");
throw new Exception("We should be on thread pool thread!");
}
// we are back on main thread
IncrementCount();
StateHasChanged(); // render!
Console.WriteLine("From thread pool " + Environment.CurrentManagedThreadId);

// we back to main thread
await InvokeAsync(() =>
{
if (Thread.CurrentThread.ManagedThreadId != 1)
{
throw new Exception("We should be on main thread again!");
}
Console.WriteLine("From UI thread " + Environment.CurrentManagedThreadId);

// we are back on main thread
IncrementCount();
StateHasChanged(); // render!
});
});
}, null, 0, 100);
}, null, 1000, 0);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
@page "/fetchdata"
@using Microsoft.AspNetCore.Components.WebAssembly.Http;
@page "/fetchdata"
@page "/fetchdata/{StartDate:datetime}"
@inject HttpClient Http


<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from the server.</p>
Expand Down Expand Up @@ -52,19 +54,30 @@ else
protected override async Task OnParametersSetAsync()
{
startDate = StartDate.GetValueOrDefault(DateTime.Now);
var url = $"sample-data/weather.json?date={startDate.ToString("yyyy-MM-dd")}";
using var req = new HttpRequestMessage(HttpMethod.Get, url);
req.SetBrowserResponseStreamingEnabled(true);

// send me to the thread pool, next line HTTP should work anyway
await Task.Delay(10).ConfigureAwait(false);

forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>(
$"sample-data/weather.json?date={startDate.ToString("yyyy-MM-dd")}");
// send the request from the UI thread
var pr = Http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
using var response = await pr;

// Because ThreadingApp doesn't really have a server endpoint to get dynamic data from,
// fake the DateFormatted values here. This would not apply in a real app.
for (var i = 0; i < forecasts.Length; i++)
await Task.Run(async () =>
{
forecasts[i].DateFormatted = startDate.AddDays(i).ToShortDateString();
}
// finish the processing in the thread pool
forecasts = await response.Content.ReadFromJsonAsync<WeatherForecast[]>();
for (var i = 0; i < forecasts.Length; i++)
{
forecasts[i].DateFormatted = startDate.AddDays(i).ToShortDateString();
}

await InvokeAsync(() =>
{
// get back to UI thread and render
ShouldRender();
return Task.CompletedTask;
});
});
}

class WeatherForecast
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@
<meta name="viewport" content="width=1024">
<title>Blazor standalone</title>
<base href="/" />
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
<link rel="stylesheet" href="css/bootstrap-icons/bootstrap-icons.min.css" />
<link rel="stylesheet" href="css/app.css" />
</head>
<body>
<app>Loading...</app>
Expand Down

0 comments on commit a764b18

Please sign in to comment.