dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.45k stars 10.03k forks source link

w3wp.exe APPCRASH - The process was terminated due to an unhandled exception: System.InvalidOperationException: The current thread is not associated with the Dispatcher. #46165

Closed Liero closed 1 year ago

Liero commented 1 year ago

Is there an existing issue for this?

Describe the bug

After I upgraded to .NET 7, my Blazor Server app deployed to IIS on premises is being restarted with following logs:

Application: w3wp.exe
CoreCLR Version: 7.0.122.56804
.NET Version: 7.0.1
Description: The process was terminated due to an unhandled exception.
Exception Info: System.InvalidOperationException: The current thread is not associated with the Dispatcher. Use InvokeAsync() to switch execution to the Dispatcher when triggering rendering or component state.

The code causing the InvalidOperation execption is follwing:

protected override void OnInitialized()
{ 
       MyObservable.
            .ObserveOn(SynchronizationContext.Current!)
            .Throttle(TimeSpan.FromMilliseconds(1000)) //switches to a new thread
            .Subscribe(() => 
            {
               _componentReference?.SomeMethod();
               StateHasChanged();
            });
}

Expected Behavior

The exception should only affect currect blazor session, not the entire blazor app.

w3wp.exe should not crash

Steps To Reproduce

  1. clone this repo: https://github.com/Liero/BlazorDispatcherException
  2. run the solution
  3. open the url in multiple browser tabs
  4. click the button "Click to crash"
  5. Click again anywhere in the page

You will see following error in all opened tabs (not just the clicked one):

image

Exceptions (if any)

System.InvalidOperationException: The current thread is not associated with the Dispatcher. Use InvokeAsync() to switch execution to the Dispatcher when triggering rendering or component state.
   at Microsoft.AspNetCore.Components.Dispatcher.AssertAccess()
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToRenderQueue(Int32 componentId, RenderFragment renderFragment)
   at Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged()
   at Sylex.ISYS.App.Pages.Warehouse.Receipts.ReceiptList.<>c__DisplayClass17_0.<<OnInitialized>b__6>d.MoveNext() in D:\a\1\s\src\app\Pages\Warehouse\Receipts\ReceiptList.razor.cs:line 77
--- End of stack trace from previous location ---
   at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__128_1(Object state)
   at System.Threading.QueueUserWorkItemCallback.Execute()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
   at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()

.NET --info


.NET SDK (reflecting any global.json):
 Version:   5.0.201
 Commit:    a09bd5c86c

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.17763
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\5.0.201\

Host:
  Version:      7.0.1
  Architecture: x64
  Commit:       97203d38ba

.NET SDKs installed:
  6.0.201 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 5.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 6.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 7.0.1 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 5.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 6.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 7.0.1 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.WindowsDesktop.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

Other architectures found:
  arm64 [C:\Program Files\dotnet]
    registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\arm64\InstallLocation]
  x86   [C:\Program Files (x86)\dotnet]
    registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation]

Environment variables:
  Not set

global.json file:
  Not found
javiercn commented 1 year ago

@Liero thanks for contacting us.

Blazor like other UI technologies uses a synchronizationcontext to ensure a consistent programming model across Wasm, Server and Desktop. For that reason you need to call StateHasChanged within the Blazor context.

In your case it is being called from a background thread that is not associated with the current circuit, which is what is causing your problem.

To solve it, you need to call .Subscribe(() => Invoke(() => StateHasChanged())); instead.

Liero commented 1 year ago

@javiercn : Yes, it's a bug in my app, but it should not terminate the whole process, should it? I'm 99% positive this wasn't the case in .NET 6.

I've created a minimal repo, see original post

javiercn commented 1 year ago

@Liero I'd be surprised if it doesn't.

An exception is happening in a background thread, we do not have a way to observe that, nor do we have context on when it is happening.

My expectation is that it indeed causes the app to crash.

Liero commented 1 year ago

In general, exception in background thread does not cause the app to crash. This is true even in blazor.

I can throw any number of custom exceptions in background threads and nothing happens to the app. The exception is swallowed, background thread exits and app continues to run

I found out, that it crashes only when trying to access component reference from background thread. If I only call StateHasChanged() from backround thread, the same InvalidOperationException is thrown, but the app continues to run.

ghost commented 1 year ago

This issue has been resolved and has not had any activity for 1 day. It will be closed for housekeeping purposes.

See our Issue Management Policies for more information.