Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Poznámka:
Toto není nejnovější verze tohoto článku. Aktuální verzi najdete ve verzi .NET 10 tohoto článku.
Varování
Tato verze ASP.NET Core se už nepodporuje. Další informace najdete v zásadách podpory .NET a .NET Core. Aktuální vydání najdete ve verzi .NET 9 tohoto článku.
Blazor používá kontext synchronizace (SynchronizationContext) k vynucování jednoho logického vlákna provádění. Metody životního cyklu komponent a zpětná volání událostí vyvolaná Blazor se provádějí v rámci kontextu synchronizace.
BlazorKontext synchronizace na straně serveru se pokouší emulovat jednovláknové prostředí tak, aby úzce odpovídal modelu WebAssembly v prohlížeči, což je jedno vlákno. Tato emulace je vymezena pouze na jednotlivé okruhy, což znamená, že dva různé okruhy mohou běžet paralelně. V každém časovém okamžiku v okruhu se práce provádí na přesně jednom vlákně, což dává dojem z jednoho logického vlákna. V tom samém okruhu neprobíhají současně žádné dvě operace.
Jedno logické vlákno spuštění neznamená jeden asynchronní tok řízení. Součást je znovu účastníkem v libovolném okamžiku, kdy očekává neúplnou Task. Metody životního cyklu nebo metody likvidace komponentů mohou být volány předtím, než asynchronní tok řízení pokračuje po čekání na dokončení operace Task. Komponenta proto musí zajistit, aby byla v platném stavu před čekáním na potenciálně neúplný Task. Komponenta musí zejména zajistit, aby byla v platném stavu pro vykreslování, když se vrátí OnInitializedAsync nebo OnParametersSetAsync. Pokud některé z těchto metod vrátí neúplnou Task, musí zajistit, aby část metody, která se dokončí synchronně, opustila komponentu v platném stavu pro vykreslení.
Další implikací znovuvstupujících komponent je, že metoda nemůže odložit Task předáním do ComponentBase.InvokeAsyncaž do doby po vrácení metody. Volání ComponentBase.InvokeAsync může odložit Task až do dosažení dalšího operátoru await.
Komponenty mohou implementovat IDisposable nebo IAsyncDisposable pro volání asynchronních metod pomocí CancellationToken z CancellationTokenSource, který je zrušen při vyřazení komponenty. To ale ve skutečnosti závisí na scénáři. Záleží na autorovi komponenty, aby se zjistilo, jestli se jedná o správné chování. Pokud například implementuje komponentu SaveButton, která při výběru tlačítka Uložit uloží některá místní data do databáze, může autor komponenty skutečně zahodit změny, pokud uživatel tlačítko vybere a rychle přejde na jinou stránku, která by mohla komponentu odstranit před dokončením asynchronního uložení.
Jednorázová komponenta může zkontrolovat odstranění po vyčkání na libovolnou Task, která neobdrží CancellationTokenod této komponenty. Neúplné objekty Taskmohou také zabránit uvolňování paměti uvolněné komponenty.
ComponentBase ignoruje výjimky způsobené zrušením Task (přesněji všechny výjimky při zrušení očekávaných Task), takže metody komponent nemusí zpracovávat TaskCanceledException a OperationCanceledException.
ComponentBase nemůže postupovat podle předchozích pokynů, protože nekomentuje, co představuje platný stav pro odvozenou komponentu a sám neimplementuje IDisposable ani IAsyncDisposable. Pokud OnInitializedAsync vrátí neúplný Task, který nepoužívá CancellationToken, a komponenta se odstraní před dokončením Task, ComponentBase přesto stále volá OnParametersSet a čeká na OnParametersSetAsync. Pokud jednorázová komponenta nepoužívá CancellationToken, OnParametersSet a OnParametersSetAsync by měly zkontrolovat, jestli je komponenta zlikvidována.
Nepoužívejte volání blokující vlákno
Obecně platí, že by se v komponentách neměly volat následující metody. Následující metody blokují vlákno provádění, a tím brání aplikaci pokračovat v práci, dokud se nedokončí základní proces Task:
Poznámka:
V příkladech v dokumentaci k architektuře Blazor, ve kterých se používají metody blokující vlákno uvedené v této části, se tyto metody používají pouze pro účely ukázky, a nikoli jako doporučené pokyny pro kódování. Například v několika ukázkách kódu komponent se k simulaci dlouho běžících procesů volá metoda Thread.Sleep.
Externí volání metod komponent za účelem aktualizace stavu
Pokud je potřeba komponentu aktualizovat na základě externí události, jako je událost časovače nebo jiné oznámení, použijte metodu InvokeAsync, která odešle provádění kódu do kontextu synchronizace architektury Blazor. Podívejte se například na následující oznamovací službu, která může o aktualizovaném stavu informovat jakoukoli naslouchající komponentu. Metodu Update je možné zavolat odkudkoli z aplikace.
TimerService.cs:
namespace BlazorSample;
public class TimerService(NotifierService notifier,
ILogger<TimerService> logger) : IDisposable
{
private int elapsedCount;
private static readonly TimeSpan heartbeatTickRate = TimeSpan.FromSeconds(5);
private readonly ILogger<TimerService> logger = logger;
private readonly NotifierService notifier = notifier;
private PeriodicTimer? timer;
public async Task Start()
{
if (timer is null)
{
timer = new(heartbeatTickRate);
logger.LogInformation("Started");
using (timer)
{
while (await timer.WaitForNextTickAsync())
{
elapsedCount += 1;
await notifier.Update("elapsedCount", elapsedCount);
logger.LogInformation("ElapsedCount {Count}", elapsedCount);
}
}
}
}
public void Stop()
{
if (timer is not null)
{
timer.Dispose();
timer = null;
logger.LogInformation("Stopped");
}
}
public void Dispose()
{
timer?.Dispose();
// The following prevents derived types that introduce a
// finalizer from needing to re-implement IDisposable.
GC.SuppressFinalize(this);
}
}
namespace BlazorSample;
public class TimerService(NotifierService notifier,
ILogger<TimerService> logger) : IDisposable
{
private int elapsedCount;
private static readonly TimeSpan heartbeatTickRate = TimeSpan.FromSeconds(5);
private readonly ILogger<TimerService> logger = logger;
private readonly NotifierService notifier = notifier;
private PeriodicTimer? timer;
public async Task Start()
{
if (timer is null)
{
timer = new(heartbeatTickRate);
logger.LogInformation("Started");
using (timer)
{
while (await timer.WaitForNextTickAsync())
{
elapsedCount += 1;
await notifier.Update("elapsedCount", elapsedCount);
logger.LogInformation("ElapsedCount {Count}", elapsedCount);
}
}
}
}
public void Dispose()
{
timer?.Dispose();
// The following prevents derived types that introduce a
// finalizer from needing to re-implement IDisposable.
GC.SuppressFinalize(this);
}
}
public class TimerService : IDisposable
{
private int elapsedCount;
private static readonly TimeSpan heartbeatTickRate = TimeSpan.FromSeconds(5);
private readonly ILogger<TimerService> logger;
private readonly NotifierService notifier;
private PeriodicTimer? timer;
public TimerService(NotifierService notifier,
ILogger<TimerService> logger)
{
this.notifier = notifier;
this.logger = logger;
}
public async Task Start()
{
if (timer is null)
{
timer = new(heartbeatTickRate);
logger.LogInformation("Started");
using (timer)
{
while (await timer.WaitForNextTickAsync())
{
elapsedCount += 1;
await notifier.Update("elapsedCount", elapsedCount);
logger.LogInformation("elapsedCount: {ElapsedCount}", elapsedCount);
}
}
}
}
public void Dispose()
{
timer?.Dispose();
}
}
public class TimerService : IDisposable
{
private int elapsedCount;
private static readonly TimeSpan heartbeatTickRate = TimeSpan.FromSeconds(5);
private readonly ILogger<TimerService> logger;
private readonly NotifierService notifier;
private PeriodicTimer? timer;
public TimerService(NotifierService notifier,
ILogger<TimerService> logger)
{
this.notifier = notifier;
this.logger = logger;
}
public async Task Start()
{
if (timer is null)
{
timer = new(heartbeatTickRate);
logger.LogInformation("Started");
using (timer)
{
while (await timer.WaitForNextTickAsync())
{
elapsedCount += 1;
await notifier.Update("elapsedCount", elapsedCount);
logger.LogInformation("elapsedCount: {ElapsedCount}", elapsedCount);
}
}
}
}
public void Dispose()
{
timer?.Dispose();
}
}
using System;
using System.Timers;
using Microsoft.Extensions.Logging;
public class TimerService : IDisposable
{
private int elapsedCount;
private readonly ILogger<TimerService> logger;
private readonly NotifierService notifier;
private Timer timer;
public TimerService(NotifierService notifier, ILogger<TimerService> logger)
{
this.notifier = notifier;
this.logger = logger;
}
public void Start()
{
if (timer is null)
{
timer = new();
timer.AutoReset = true;
timer.Interval = 10000;
timer.Elapsed += HandleTimer;
timer.Enabled = true;
logger.LogInformation("Started");
}
}
private async void HandleTimer(object source, ElapsedEventArgs e)
{
elapsedCount += 1;
await notifier.Update("elapsedCount", elapsedCount);
logger.LogInformation("elapsedCount: {ElapsedCount}", elapsedCount);
}
public void Dispose()
{
timer?.Dispose();
}
}
using System;
using System.Timers;
using Microsoft.Extensions.Logging;
public class TimerService : IDisposable
{
private int elapsedCount;
private readonly ILogger<TimerService> logger;
private readonly NotifierService notifier;
private Timer timer;
public TimerService(NotifierService notifier, ILogger<TimerService> logger)
{
this.notifier = notifier;
this.logger = logger;
}
public void Start()
{
if (timer is null)
{
timer = new Timer();
timer.AutoReset = true;
timer.Interval = 10000;
timer.Elapsed += HandleTimer;
timer.Enabled = true;
logger.LogInformation("Started");
}
}
private async void HandleTimer(object source, ElapsedEventArgs e)
{
elapsedCount += 1;
await notifier.Update("elapsedCount", elapsedCount);
logger.LogInformation("elapsedCount: {ElapsedCount}", elapsedCount);
}
public void Dispose()
{
timer?.Dispose();
}
}
NotifierService.cs:
namespace BlazorSample;
public class NotifierService
{
public async Task Update(string key, int value)
{
if (Notify != null)
{
await Notify.Invoke(key, value);
}
}
public event Func<string, int, Task>? Notify;
}
namespace BlazorSample;
public class NotifierService
{
public async Task Update(string key, int value)
{
if (Notify != null)
{
await Notify.Invoke(key, value);
}
}
public event Func<string, int, Task>? Notify;
}
public class NotifierService
{
public async Task Update(string key, int value)
{
if (Notify != null)
{
await Notify.Invoke(key, value);
}
}
public event Func<string, int, Task>? Notify;
}
public class NotifierService
{
public async Task Update(string key, int value)
{
if (Notify != null)
{
await Notify.Invoke(key, value);
}
}
public event Func<string, int, Task>? Notify;
}
using System;
using System.Threading.Tasks;
public class NotifierService
{
public async Task Update(string key, int value)
{
if (Notify != null)
{
await Notify.Invoke(key, value);
}
}
public event Func<string, int, Task> Notify;
}
using System;
using System.Threading.Tasks;
public class NotifierService
{
public async Task Update(string key, int value)
{
if (Notify != null)
{
await Notify.Invoke(key, value);
}
}
public event Func<string, int, Task> Notify;
}
Registrace služeb:
Pro vývoj na straně klienta zaregistrujte služby jako singletony v souboru na straně
Programklienta:builder.Services.AddSingleton<NotifierService>(); builder.Services.AddSingleton<TimerService>();Pro vývoj na straně serveru zaregistrujte služby jako vymezené v souboru serveru
Program:builder.Services.AddScoped<NotifierService>(); builder.Services.AddScoped<TimerService>();
K aktualizaci komponenty použijte NotifierService.
Notifications.razor:
@page "/notifications"
@implements IDisposable
@inject NotifierService Notifier
@inject TimerService Timer
<PageTitle>Notifications</PageTitle>
<h1>Notifications Example</h1>
<h2>Timer Service</h2>
<button @onclick="StartTimer">Start Timer</button>
<button @onclick="StopTimer">Stop Timer</button>
<h2>Notifications</h2>
<p>
Status:
@if (lastNotification.key is not null)
{
<span>@lastNotification.key = @lastNotification.value</span>
}
else
{
<span>Awaiting notification</span>
}
</p>
@code {
private (string key, int value) lastNotification;
protected override void OnInitialized() => Notifier.Notify += OnNotify;
public async Task OnNotify(string key, int value)
{
await InvokeAsync(() =>
{
lastNotification = (key, value);
StateHasChanged();
});
}
private void StartTimer() => _ = Task.Run(Timer.Start);
private void StopTimer() => Timer.Stop();
public void Dispose() => Notifier.Notify -= OnNotify;
}
Notifications.razor:
@page "/notifications"
@implements IDisposable
@inject NotifierService Notifier
@inject TimerService Timer
<PageTitle>Notifications</PageTitle>
<h1>Notifications Example</h1>
<h2>Timer Service</h2>
<button @onclick="StartTimer">Start Timer</button>
<h2>Notifications</h2>
<p>
Status:
@if (lastNotification.key is not null)
{
<span>@lastNotification.key = @lastNotification.value</span>
}
else
{
<span>Awaiting notification</span>
}
</p>
@code {
private (string key, int value) lastNotification;
protected override void OnInitialized() => Notifier.Notify += OnNotify;
public async Task OnNotify(string key, int value)
{
await InvokeAsync(() =>
{
lastNotification = (key, value);
StateHasChanged();
});
}
private void StartTimer() => _ = Task.Run(Timer.Start);
public void Dispose() => Notifier.Notify -= OnNotify;
}
ReceiveNotifications.razor:
@page "/receive-notifications"
@implements IDisposable
@inject NotifierService Notifier
@inject TimerService Timer
<h1>Receive Notifications</h1>
<h2>Timer Service</h2>
<button @onclick="StartTimer">Start Timer</button>
<h2>Notifications</h2>
<p>
Status:
@if (lastNotification.key is not null)
{
<span>@lastNotification.key = @lastNotification.value</span>
}
else
{
<span>Awaiting notification</span>
}
</p>
@code {
private (string key, int value) lastNotification;
protected override void OnInitialized()
{
Notifier.Notify += OnNotify;
}
public async Task OnNotify(string key, int value)
{
await InvokeAsync(() =>
{
lastNotification = (key, value);
StateHasChanged();
});
}
private void StartTimer()
{
_ = Task.Run(Timer.Start);
}
public void Dispose()
{
Notifier.Notify -= OnNotify;
}
}
ReceiveNotifications.razor:
@page "/receive-notifications"
@implements IDisposable
@inject NotifierService Notifier
@inject TimerService Timer
<h1>Receive Notifications</h1>
<h2>Timer Service</h2>
<button @onclick="StartTimer">Start Timer</button>
<h2>Notifications</h2>
<p>
Status:
@if (lastNotification.key is not null)
{
<span>@lastNotification.key = @lastNotification.value</span>
}
else
{
<span>Awaiting notification</span>
}
</p>
@code {
private (string key, int value) lastNotification;
protected override void OnInitialized()
{
Notifier.Notify += OnNotify;
}
public async Task OnNotify(string key, int value)
{
await InvokeAsync(() =>
{
lastNotification = (key, value);
StateHasChanged();
});
}
private void StartTimer()
{
_ = Task.Run(Timer.Start);
}
public void Dispose()
{
Notifier.Notify -= OnNotify;
}
}
ReceiveNotifications.razor:
@page "/receive-notifications"
@implements IDisposable
@inject NotifierService Notifier
@inject TimerService Timer
<h1>Receive Notifications</h1>
<h2>Timer Service</h2>
<button @onclick="StartTimer">Start Timer</button>
<h2>Notifications</h2>
<p>
Status:
@if (lastNotification.key is not null)
{
<span>@lastNotification.key = @lastNotification.value</span>
}
else
{
<span>Awaiting notification</span>
}
</p>
@code {
private (string key, int value) lastNotification;
protected override void OnInitialized()
{
Notifier.Notify += OnNotify;
}
public async Task OnNotify(string key, int value)
{
await InvokeAsync(() =>
{
lastNotification = (key, value);
StateHasChanged();
});
}
private void StartTimer()
{
Timer.Start();
}
public void Dispose()
{
Notifier.Notify -= OnNotify;
}
}
ReceiveNotifications.razor:
@page "/receive-notifications"
@implements IDisposable
@inject NotifierService Notifier
@inject TimerService Timer
<h1>Receive Notifications</h1>
<h2>Timer Service</h2>
<button @onclick="StartTimer">Start Timer</button>
<h2>Notifications</h2>
<p>
Status:
@if (lastNotification.key != null)
{
<span>@lastNotification.key = @lastNotification.value</span>
}
else
{
<span>Awaiting notification</span>
}
</p>
@code {
private (string key, int value) lastNotification;
protected override void OnInitialized()
{
Notifier.Notify += OnNotify;
}
public async Task OnNotify(string key, int value)
{
await InvokeAsync(() =>
{
lastNotification = (key, value);
StateHasChanged();
});
}
private void StartTimer()
{
Timer.Start();
}
public void Dispose()
{
Notifier.Notify -= OnNotify;
}
}
V předchozím příkladu:
- Časovač je inicializován mimo Blazorkontext synchronizace s
_ = Task.Run(Timer.Start). -
NotifierServicevyvolá metoduOnNotifykomponenty.InvokeAsyncslouží k přepnutí na správný kontext a zařazení do fronty rerender. Další informace najdete v tématu Vykreslování komponent ASP.NET Core Razor. - Komponenta implementuje IDisposable. Ve funkci
OnNotify, která je volána při odstranění komponenty architekturou, je delegátDisposeodregistrován. Další informace najdete v tématu ASP.NET Core odstranění komponenty Razor.
-
NotifierServicezavolá metoduOnNotifykomponenty mimo kontext synchronizace Blazor.InvokeAsyncslouží k přepnutí na správný kontext a zařazení do fronty rerender. Další informace najdete v tématu Vykreslování komponent ASP.NET Core Razor. - Komponenta implementuje IDisposable. Ve funkci
OnNotify, která je volána při odstranění komponenty architekturou, je delegátDisposeodregistrován. Další informace najdete v tématu ASP.NET Core odstranění komponenty Razor.
Důležité
Razor Pokud komponenta definuje událost aktivovanou z vlákna na pozadí, může být potřeba, aby komponenta zachytila a obnovila kontext spuštění (ExecutionContext) v době registrace obslužné rutiny. Další informace najdete v tématu Volání InvokeAsync(StateHasChanged) způsobí, že se stránka vrátí do výchozí jazykové verze (dotnet/aspnetcore #28521).
Pokud chcete odeslat zachycené výjimky z pozadí TimerService do komponenty, aby se s nimi zacházelo jako s běžnými výjimkami životního cyklu, podívejte se na sekci Řešení zachycených výjimek mimo životní cyklus komponenty Razor.
Zpracování zachycených výjimek mimo Razor životní cyklus komponenty
Použijte ComponentBase.DispatchExceptionAsync v komponentě Razor ke zpracování výjimek, které jsou vyvolány mimo zásobník volání životního cyklu komponenty. To umožňuje kódu komponenty zacházet s výjimkami, jako by se jedná o výjimky metody životního cyklu. Následně mohou mechanismy zpracování chyb Blazor, jako jsou omezení chyb, zpracovávat výjimky.
Poznámka:
ComponentBase.DispatchExceptionAsync se používá v Razor souborech komponent (.razor), které dědí z ComponentBase. Při vytváření komponent, které implement IComponent directly, použijte RenderHandle.DispatchExceptionAsync.
Pokud chcete zpracovat zachycené výjimky mimo Razor životní cyklu komponenty, předejte výjimku DispatchExceptionAsync a čekejte na výsledek:
try
{
...
}
catch (Exception ex)
{
await DispatchExceptionAsync(ex);
}
Běžným scénářem předchozího přístupu je situace, kdy komponenta zahájí asynchronní operaci, ale nečeká na Task, což se často nazývá vzor fire and forget, protože se operace spustí (zahájí) a její výsledek se zapomene (ignoruje). Pokud operace selže, můžete chtít, aby komponenta zachází se selháním jako s výjimkou životního cyklu komponent pro některý z následujících cílů:
- Vložte komponentu do chybového stavu, například pro aktivaci error boundary.
- Ukončete okruh, pokud neexistuje žádná hranice chyby.
- Aktivujte stejné protokolování, ke kterému dochází u výjimek životního cyklu.
V následujícím příkladu uživatel vybere tlačítko Odeslat sestavu a aktivuje metodu pozadí, ReportSender.SendAsynckterá odešle sestavu. Ve většině případů komponenta čeká na Task asynchronní volání a aktualizuje uživatelské rozhraní tak, aby indikuje dokončení operace. V následujícím příkladu metoda SendReport neočeká Task a nehlásí výsledek uživateli. Vzhledem k tomu, že komponenta záměrně zahodí Task v SendReport, k asynchronním selháním dochází mimo běžný zásobník volání životního cyklu, a proto je Blazor nevidí:
<button @onclick="SendReport">Send report</button>
@code {
private void SendReport()
{
_ = ReportSender.SendAsync();
}
}
Pokud chcete zacházet se selháními, jako jsou výjimky metody životního cyklu, explicitně odešlete výjimky zpět do komponenty DispatchExceptionAsync, jak ukazuje následující příklad:
<button @onclick="SendReport">Send report</button>
@code {
private void SendReport()
{
_ = SendReportAsync();
}
private async Task SendReportAsync()
{
try
{
await ReportSender.SendAsync();
}
catch (Exception ex)
{
await DispatchExceptionAsync(ex);
}
}
}
Alternativní přístup využívá Task.Run:
private void SendReport()
{
_ = Task.Run(async () =>
{
try
{
await ReportSender.SendAsync();
}
catch (Exception ex)
{
await DispatchExceptionAsync(ex);
}
});
}
Pro funkční ukázku implementujte příklad oznámení časovače v metodách vyvolání komponent externě za účelem aktualizace stavu.
Blazor V aplikaci přidejte následující soubory z příkladu oznámení časovače a zaregistrujte služby v Program souboru, jak vysvětluje část:
TimerService.csNotifierService.csNotifications.razor
Příklad používá časovač mimo
Nejprve změňte kód TimerService.cs tak, aby se vytvořila umělá výjimka mimo životní cyklus komponenty.
while Ve smyčce TimerService.csvyvolá výjimku, když elapsedCount dosáhne hodnoty dvou:
if (elapsedCount == 2)
{
throw new Exception("I threw an exception! Somebody help me!");
}
Umístěte hranici chyby do hlavního rozvržení aplikace. Nahraďte <article>...</article> značkování následujícím značkováním.
V MainLayout.razor:
<article class="content px-4">
<ErrorBoundary>
<ChildContent>
@Body
</ChildContent>
<ErrorContent>
<p class="alert alert-danger" role="alert">
Oh, dear! Oh, my! - George Takei
</p>
</ErrorContent>
</ErrorBoundary>
</article>
V Blazor Web Apps chybovou hranicí použitou pouze pro statickou MainLayout komponentu je hranice aktivní pouze během fáze statického vykreslování na straně serveru (static SSR). Hranice se neaktivuje, protože komponenta dále v hierarchii komponent je interaktivní. Pokud chcete obecně povolit interaktivitu pro komponentu MainLayout a ostatní komponenty dále v hierarchii, povolte interaktivní vykreslování instancí komponent HeadOutlet a Routes v komponentě App (Components/App.razor). Následující příklad přijímá režim vykreslování Interactive Server (InteractiveServer):
<HeadOutlet @rendermode="InteractiveServer" />
...
<Routes @rendermode="InteractiveServer" />
Pokud v tomto okamžiku spustíte aplikaci, vyvolá se výjimka, když uplynulý počet dosáhne hodnoty 2. Uživatelské rozhraní se ale nezmění. Hranice chyby nezobrazuje obsah chyby.
Pokud chcete odesílat výjimky ze služby časovače zpět do Notifications komponenty, provede se v této komponentě následující změny:
- Spusťte časovač v
try-catchpříkazu. V klauzulicatchbloku se výjimky odesílají zpět do komponenty předánímtry-catchdo Exception a čekáním na výsledek. - V metodě
StartTimerspusťte asynchronní službu časovače v delegátu ActionTask.Run, a úmyslně zahoďte vrácený Task.
StartTimer Metoda Notifications komponenty (Notifications.razor):
private void StartTimer()
{
_ = Task.Run(async () =>
{
try
{
await Timer.Start();
}
catch (Exception ex)
{
await DispatchExceptionAsync(ex);
}
});
}
Když se služba časovače spustí a dosáhne počtu dvou, výjimka se odešle do Razor komponenty, což následně aktivuje hranici chyby, aby zobrazila chybový obsah komponenty <ErrorBoundary> uvnitř komponenty MainLayout.
Oh, drahoušku! Ach, můj bože! - George Takei