Цепи Blazor Server теперь сами приостанавливаются, когда вкладка простаивает
В .NET 11 Preview 7 появился подключаемый пакет, который приостанавливает интерактивные Server-цепи, пока вкладка браузера скрыта, освобождая память и соединения SignalR, занятые пользователями, которых на самом деле нет.
.NET 11 Preview 7 вышел 2026-08-11, и в разделе ASP.NET Core спрятано решение одной из самых давних проблем ёмкости Blazor Server: цепь, на которую никто не смотрит, стоит ровно столько же, сколько цепь, которой кто-то пользуется. В заметках о выпуске ASP.NET Core Preview 7 описана автоматическая пауза, появившаяся благодаря dotnet/aspnetcore#64886.
Скрытая вкладка и отключённая вкладка не одно и то же
Blazor Server хранит состояние каждого пользователя в цепи на сервере, и эта цепь живёт ровно столько, сколько живёт соединение SignalR. Когда пользователь переключается на другую вкладку и забывает о вашей, WebSocket не закрывается. Настольные браузеры спокойно держат его открытым часами. Цепь сохраняет своё дерево компонентов, свою область внедрения зависимостей, свою очередь отрисовки и своё место в вашем бюджете параллелизма, и всё это ради пользователя, который ушёл на обед.
Автоматическая пауза вместо этого опирается на сигнал видимости из браузера. Когда вкладка скрыта дольше настраиваемой задержки, клиент просит сервер приостановить цепь, и та освобождается. Когда пользователь возвращается, цепь возобновляется.
Как включить
Функция подключается явно и живёт в отдельном пакете:
<PackageReference Include="Microsoft.AspNetCore.Components.Server.AutoPause" />
Настройка задаётся при регистрации режима отрисовки:
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.WithBrowserOptions(options =>
{
options.AddAutoPause(pause =>
{
pause.Enabled = true; // default
pause.HiddenDelay = TimeSpan.FromSeconds(30); // default is 2 minutes
});
});
Значение HiddenDelay по умолчанию равно двум минутам. Снижение до 30 секунд быстрее возвращает память ценой большего числа обращений на возобновление от пользователей, которые постоянно переключаются между вкладками.
Случаи, когда пауза не срабатывает
Самое интересное здесь в том, от чего автоматическая пауза отказывается. Она откладывает приостановку, если фокус находится в текстовом поле или в элементе contenteditable, если воспроизводится незаглушённое аудио или видео, если открыто окно Picture-in-Picture, если удерживается Web Lock, а также пока в цепи есть незавершённая активность, например вызов IJSRuntime или передача потока. Иначе говоря, скрытая вкладка, которая всё ещё делает что-то по поручению пользователя, из-под него не выдёргивается.
Собственную логику отсрочки можно добавить из инициализатора JavaScript:
// wwwroot/{ASSEMBLY NAME}.lib.module.js
export function beforeWebStart(options) {
options.circuit ??= {};
options.circuit.circuitHandlers ??= [];
options.circuit.circuitHandlers.push({
onCircuitPausing: async (signal) => {
await savePendingWork(signal);
},
});
}
Объект signal прерывается, если пауза отменена, например потому что вкладка снова стала видимой, пока ваш обработчик ещё сохранял данные. На стороне сервера Circuit.RequestCircuitPauseAsync теперь возвращает Task<bool> и принимает необязательный токен отмены, так что работу по отсрочке можно отменить при обрыве соединения.
Что проверить перед включением
Автоматическая пауза построена на механизме приостановки и возобновления, появившемся в .NET 10, а значит возобновление восстанавливает цепь из сохранённого состояния компонентов. Всё, что компонент держит в обычном поле и никогда не объявляет как сохраняемое, после паузы исчезает. Проверьте компоненты с состоянием, прежде чем включать это в продакшене, и следите за телеметрией переподключений: сценарий отказа здесь очень похож на цепь, которая отключилась сама.
Preview 7 получился насыщенным. В той же поставке на стороне C# появились break и continue с меткой.
Comments
Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.