Skip to content

Blazor WASM app with Timers / Async code and ClientWebSocket results in increasing CPU utilisation.  #62227

@m-chandler

Description

@m-chandler

Describe the bug

We had to abort our upgrade to NET6.0 due to performance concerns. The upgraded apps were using significantly higher CPU. I've finally gotten around to identifying the cause and replicating it in a sample project.

The symptom is increasing CPU utilisation throughout the day. We have multiple blazor apps which are run all day, so this is a noticable issue to us.

This appears to be caused by asyncronous code (e.g. System.Threading.Timer, async / await) being used whilst an active ClientWebSocket connection is open. When these two elements are in play a large number of calls are made to "Install Timer" in the browser, and this number increases logarithmically throughout the day. Note: I'm using the Chrome Performance tab to verify this; profiling for a short period of time and then searching for the number of calls to "Install Timer".

Say you had a component set up a timer to do something every 500ms:

    public partial class Index
    {
        private Timer _timer;

        public Index()
        {
            _timer = new System.Threading.Timer(_ =>
            {
                Console.WriteLine("In timer, doing stuff.");
            }, null, 0, 500);
        }
    }

Ignoring the fact that the timer is never diposed, the above code causes no issues. You can head into the Chrome Performance profiler (or whatever browser you prefer), and notice that there are a reasonable number of calls to "Install Timer". There's more than expected in my example beacuse I tried to turn off BrowserLink and failed, but I digress. 55 is a reasonable number.

good-timers

Now add on a ClientWebSocket Connection wrapped in a service:

    public class ApiService
    {
        public ApiService()
        {
            var websocket = new ClientWebSocket();
            Task.Run(async () =>
            {
                await websocket.ConnectAsync(
                    new Uri("wss://websocket-echo.glitch.me"),
                    CancellationToken.None);
                Console.WriteLine("Connected.");
            });
        }
    }

Again - ordinarily this code would be fine (and without the Timer in our component, it is fine). Unfortunately when both of the above snippets are used our System.Threading.Timer is now exhibiting serious performance issues.

Profiling the app for 10 seconds every minute results in the below number of calls to "Install Timer".
00:30:00->00:40:00: 422 calls.
01:30:00->01:40:00: 1548 calls.
02:30:00->02:40:00: 2823 calls.
03:30:00->03:40:00: 4023 calls.

4023 "Install Timer" calls over 10 seconds, for a Timer that does something every 500ms?

You can start to visually see the performance issue develop when comparing the below pic, execution times are quite a bit thicker than the previous example.

bad-timers

To Reproduce

https://github.com/m-chandler/blazor-timers-net6.0

Exceptions (if any)

N/A

Further technical details

  • ASP.NET Core version: NET 6.0
  • The IDE (VS / VS Code/ VS4Mac) you're running on, and its version: VS 2022 Pro
  • Include the output of dotnet --info:
dotnet --info Output
.NET SDK (reflecting any global.json):
 Version:   6.0.100
 Commit:    9e8b04bbff

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

Host (useful for support):
  Version: 6.0.0
  Commit:  4822e3c3aa

.NET SDKs installed:
  5.0.302 [C:\Program Files\dotnet\sdk]
  5.0.403 [C:\Program Files\dotnet\sdk]
  6.0.100-rc.1.21463.6 [C:\Program Files\dotnet\sdk]
  6.0.100 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 5.0.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 6.0.0-rc.1.21452.15 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 6.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 5.0.12 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 6.0.0-rc.1.21451.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 6.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.WindowsDesktop.App 5.0.12 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 6.0.0-rc.1.21451.3 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 6.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

To install additional .NET runtimes or SDKs:
  https://aka.ms/dotnet-download

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions