Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Catatan
Ini bukan versi terbaru dari artikel ini. Untuk rilis saat ini, lihat versi .NET 9 dari artikel ini.
Peringatan
Versi ASP.NET Core ini tidak lagi didukung. Untuk informasi selengkapnya, lihat Kebijakan Dukungan .NET dan .NET Core. Untuk rilis saat ini, lihat versi .NET 9 dari artikel ini.
Penting
Informasi ini berkaitan dengan produk pra-rilis yang mungkin dimodifikasi secara substansial sebelum dirilis secara komersial. Microsoft tidak memberikan jaminan, tersirat maupun tersurat, sehubungan dengan informasi yang diberikan di sini.
Untuk rilis saat ini, lihat versi .NET 9 dari artikel ini.
Blazor menggunakan konteks sinkronisasi (SynchronizationContext) untuk memberlakukan utas eksekusi logis tunggal. Metode siklus hidup komponen dan panggilan balik peristiwa yang dimunculkan oleh Blazor dijalankan pada konteks sinkronisasi.
BlazorKonteks sinkronisasi sisi server mencoba mensimulasikan lingkungan utas tunggal agar lebih sesuai dengan model WebAssembly di browser, yang menggunakan utas tunggal. Emulasi ini hanya dilingkup ke sirkuit individual, yang berarti dua sirkuit yang berbeda dapat berjalan secara paralel. Pada setiap titik waktu dalam suatu sirkuit, tugas dilakukan pada tepat satu utas, sehingga memberikan kesan adanya satu utas logis. Tidak ada dua operasi yang dijalankan secara bersamaan dalam sirkuit yang sama.
Satu alur eksekusi logis tidak menyiratkan satu alur kontrol asinkron. Komponen bersifat re-entrant pada setiap titik di mana ia menunggu Taskyang belum lengkap. Metode siklus hidup atau metode pembuangan komponen dapat dipanggil sebelum alur kontrol asinkron dilanjutkan setelah menunggu Task selesai. Oleh karena itu, komponen harus memastikan bahwa dirinya dalam keadaan valid sebelum menanti status Taskyang berpotensi tidak lengkap. Secara khusus, komponen harus memastikan bahwa komponen berada dalam status valid untuk penyajian saat OnInitializedAsync atau OnParametersSetAsync kembali. Jika salah satu metode ini mengembalikan Taskyang tidak lengkap , mereka harus memastikan bahwa bagian dari metode yang selesai secara sinkron meninggalkan komponen dalam keadaan valid untuk penyajian.
Implikasi tambahan dari komponen re-entrant adalah bahwa metode tidak dapat menangguhkan Task sampai setelah metode mengembalikan dengan mengoper ke ComponentBase.InvokeAsync. Memanggil ComponentBase.InvokeAsync hanya dapat menunda Task hingga operator await
berikutnya tercapai.
Komponen mungkin menerapkan IDisposable atau IAsyncDisposable untuk memanggil metode asinkron menggunakan CancellationToken dari CancellationTokenSource yang dibatalkan saat komponen dibuang. Namun, ini benar-benar tergantung pada skenarionya. Terserah pembuat komponen untuk menentukan apakah itu perilaku yang benar. Misalnya, jika menerapkan komponen SaveButton
yang mempertahankan beberapa data lokal ke database ketika tombol simpan dipilih, pembuat komponen mungkin memang berniat untuk membuang perubahan jika pengguna memilih tombol dan dengan cepat menavigasi ke halaman lain, yang dapat membuang komponen sebelum penyimpanan asinkron selesai.
Komponen sekali pakai dapat memeriksa pembuangan setelah menunggu setiap Task yang tidak menerima CancellationTokendari komponen tersebut. Taskyang tidak lengkap juga dapat mencegah pengumpulan sampah komponen yang dibuang.
ComponentBase mengabaikan pengecualian yang disebabkan oleh pembatalan Task (lebih tepatnya, ini mengabaikan semua pengecualian jika Taskyang ditunggu dibatalkan), sehingga metode komponen tidak perlu menangani TaskCanceledException dan OperationCanceledException.
ComponentBase tidak dapat mengikuti panduan sebelumnya karena tidak mengonsepkan apa yang merupakan status yang valid untuk komponen turunan dan itu sendiri tidak menerapkan IDisposable atau IAsyncDisposable. Jika OnInitializedAsync mengembalikan Task yang tidak lengkap yang tidak menggunakan CancellationToken dan komponen dibuang sebelum Task selesai, ComponentBase masih memanggil OnParametersSet dan menunggu OnParametersSetAsync. Jika komponen sekali pakai tidak menggunakan CancellationToken, maka OnParametersSet dan OnParametersSetAsync harus memeriksa apakah komponen tersebut sudah dibuang.
Hindari panggilan yang membuat utas terblokir
Secara umum, jangan panggil metode berikut dalam komponen. Metode berikut memblokir utas eksekusi dan dengan demikian memblokir aplikasi agar tidak melanjutkan pekerjaan hingga Task yang mendasarinya selesai:
Catatan
Contoh dokumentasi Blazor yang menggunakan metode pemblokiran utas yang disebutkan di bagian ini hanya menggunakan metode untuk tujuan demonstrasi, bukan sebagai panduan pengodean yang disarankan. Misalnya, beberapa demonstrasi kode komponen mensimulasikan proses yang berjalan lama dengan memanggil Thread.Sleep.
Memanggil metode komponen secara eksternal untuk memperbarui status
Jika komponen harus diperbarui berdasarkan peristiwa eksternal, seperti timer atau pemberitahuan lainnya, gunakan metode InvokeAsync
, yang mengirimkan eksekusi kode ke konteks sinkronisasi Blazor. Misalnya, pertimbangkan layanan pemberitahuan berikut yang dapat memberi tahu komponen apa pun yang mendengarkan tentang status yang diperbarui. Metode Update
dapat dipanggil dari mana saja di aplikasi.
TimerService.cs
:
namespace BlazorSample;
public class TimerService(NotifierService notifier,
ILogger<TimerService> logger) : IDisposable
{
private int elapsedCount;
private readonly static 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);
}
}
namespace BlazorSample;
public class TimerService(NotifierService notifier,
ILogger<TimerService> logger) : IDisposable
{
private int elapsedCount;
private readonly static 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 readonly static 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 readonly static 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;
}
Daftarkan layanan:
Untuk pengembangan sisi klien, daftarkan layanan sebagai singleton di file
Program
klien.builder.Services.AddSingleton<NotifierService>(); builder.Services.AddSingleton<TimerService>();
Untuk pengembangan sisi server, daftarkan layanan sebagaimana dilingkup dalam file server
Program
:builder.Services.AddScoped<NotifierService>(); builder.Services.AddScoped<TimerService>();
Gunakan NotifierService
untuk memperbarui komponen.
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;
}
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;
}
}
Dalam contoh sebelumnya:
- Timer dimulai di luar konteks sinkronisasi Blazor dengan
_ = Task.Run(Timer.Start)
. -
NotifierService
memanggil metode komponenOnNotify
.InvokeAsync
digunakan untuk beralih ke konteks yang benar dan mengantrikan pengulangan render. Untuk informasi lebih lanjut, lihat perenderan komponen Razor ASP.NET Core. - Komponen mengimplementasikan IDisposable. Delegasi
OnNotify
tidak berlangganan lagi pada metodeDispose
, yang dipanggil oleh framework saat komponen dibuang. Untuk informasi selengkapnya, lihat RazorASP.NET Core .
-
NotifierService
memanggil metodeOnNotify
komponen di luar konteks sinkronisasi Blazor.InvokeAsync
digunakan untuk beralih ke konteks yang benar dan mengantrekan rerender. Untuk informasi lebih lanjut, lihat perenderan komponen Razor ASP.NET Core. - Komponen mengimplementasikan IDisposable. Delegasi
OnNotify
berhenti berlangganan dalam metodeDispose
, yang dipanggil oleh kerangka kerja saat komponen dibuang. Untuk informasi selengkapnya, lihat RazorASP.NET Core .
Penting
Razor Jika komponen mendefinisikan peristiwa yang dipicu dari utas latar belakang, komponen mungkin perlu menangkap dan memulihkan konteks eksekusi (ExecutionContext) pada saat pendaftaran handler. Untuk informasi selengkapnya, lihat Pemanggilan InvokeAsync(StateHasChanged)
menyebabkan halaman kembali ke budaya default (dotnet/aspnetcore #28521).
Untuk mengirimkan pengecualian yang tertangkap dari latar belakang TimerService
ke komponen untuk memperlakukan pengecualian seperti pengecualian peristiwa siklus hidup normal, lihat bagian Menangani pengecualian yang tertangkap di luar Razor siklus hidup komponen.
Menangani pengecualian yang tertangkap di luar Razor siklus hidup komponen
Gunakan ComponentBase.DispatchExceptionAsync dalam komponen Razor untuk memproses pengecualian yang dilemparkan di luar tumpukan panggilan siklus hidup komponen. Ini memungkinkan kode komponen untuk memperlakukan pengecualian seolah-olah mereka adalah pengecualian metode siklus hidup. Setelah itu, Blazormekanisme penanganan kesalahan, seperti batas kesalahan, dapat memproses pengecualian.
Catatan
ComponentBase.DispatchExceptionAsync digunakan dalam Razor file komponen (.razor
) yang mewarisi dari ComponentBase. Saat membuat komponen yang implement IComponent directly, gunakan RenderHandle.DispatchExceptionAsync.
Untuk menangani pengecualian yang tertangkap di luar Razor siklus hidup komponen, berikan pengecualian ke DispatchExceptionAsync dan tunggu hasilnya:
try
{
...
}
catch (Exception ex)
{
await DispatchExceptionAsync(ex);
}
Skenario umum untuk pendekatan sebelumnya adalah ketika komponen memulai operasi asinkron tetapi tidak menggunakan 'await' Task, sering disebut pola 'fire and forget' karena metode tersebut 'ditembakkan' (dimulai) dan hasil metode tersebut diabaikan (dibuang). Jika operasi gagal, Anda mungkin ingin komponen memperlakukan kegagalan sebagai pengecualian siklus hidup komponen untuk salah satu tujuan berikut:
- Masukkan komponen ke dalam status rusak, misalnya, untuk memicu batas kesalahan.
- Hentikan sirkuit jika tidak ada batas kesalahan.
- Memicu pencatatan yang sama seperti yang terjadi untuk pengecualian siklus hidup.
Dalam contoh berikut, pengguna memilih tombol Kirim laporan untuk memicu metode latar belakang, ReportSender.SendAsync
, yang mengirim laporan. Dalam kebanyakan kasus, komponen menunggu Task dari panggilan asinkron dan memperbarui UI untuk menunjukkan operasi selesai. Dalam contoh berikut, SendReport
metode tidak menunggu Task dan tidak melaporkan hasilnya kepada pengguna. Karena komponen sengaja membuang Task di dalam SendReport
, kegagalan asinkron apa pun terjadi di luar tumpukan panggilan siklus hidup normal, sehingga tidak terlihat oleh Blazor.
<button @onclick="SendReport">Send report</button>
@code {
private void SendReport()
{
_ = ReportSender.SendAsync();
}
}
Untuk memperlakukan kegagalan seperti pengecualian metode siklus hidup, secara eksplisit mengirimkan kembali pengecualian ke komponen dengan DispatchExceptionAsync, seperti yang ditunjukkan contoh berikut:
<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);
}
}
}
Pendekatan alternatif memanfaatkan Task.Run:
private void SendReport()
{
_ = Task.Run(async () =>
{
try
{
await ReportSender.SendAsync();
}
catch (Exception ex)
{
await DispatchExceptionAsync(ex);
}
});
}
Untuk demonstrasi yang berfungsi, terapkan contoh pemberitahuan timer di Memanggil metode komponen secara eksternal untuk memperbarui status. Di aplikasi Blazor , tambahkan file berikut dari contoh pemberitahuan timer dan daftarkan layanan dalam Program
file seperti yang dijelaskan bagian:
TimerService.cs
NotifierService.cs
Notifications.razor
Contoh menggunakan timer di luar siklus hidup komponen
Pertama, ubah kode untuk TimerService.cs
membuat pengecualian buatan di luar siklus hidup komponen. Dalam perulangan while
TimerService.cs
, berikan pengecualian ketika elapsedCount
mencapai nilai dua:
if (elapsedCount == 2)
{
throw new Exception("I threw an exception! Somebody help me!");
}
Tempatkan batas kesalahan di tata letak utama aplikasi.
<article>...</article>
Ganti markup dengan markup berikut.
Di 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>
Dalam Blazor Web App ketika batas kesalahan hanya diterapkan ke komponen MainLayout
yang statis, batas tersebut hanya aktif selama fase rendering sisi server statis (SSR statis). Batas tidak akan aktif hanya karena ada komponen yang lebih dalam dalam hierarki komponen yang bersifat interaktif. Untuk mengaktifkan interaktivitas secara luas untuk komponen MainLayout
dan komponen lainnya lebih jauh ke hierarki komponen, aktifkan penyajian interaktif untuk instans komponen HeadOutlet
dan Routes
di komponen App
(Components/App.razor
). Contoh berikut mengadopsi mode render Server Interaktif (InteractiveServer
):
<HeadOutlet @rendermode="InteractiveServer" />
...
<Routes @rendermode="InteractiveServer" />
Jika Anda menjalankan aplikasi pada saat ini, pengecualian akan terjadi saat jumlah yang berlalu mencapai nilai dua. Namun, UI tidak berubah. Batas kesalahan tidak menampilkan konten kesalahan.
Untuk mengirimkan pengecualian dari layanan timer kembali ke Notifications
komponen, perubahan berikut dilakukan pada komponen:
- Mulai timer dalam
try-catch
instruksi. Dalam klausulcatch
di bloktry-catch
, pengecualian diteruskan kembali ke komponen dengan meneruskan Exception ke DispatchExceptionAsync dan menunggu hasilnya. - Dalam metode
StartTimer
, mulai layanan timer asinkron di delegasi ActionTask.Run dan sengaja membuang Task yang dikembalikan.
Metode StartTimer
dari komponen Notifications
(Notifications.razor
):
private void StartTimer()
{
_ = Task.Run(async () =>
{
try
{
await Timer.Start();
}
catch (Exception ex)
{
await DispatchExceptionAsync(ex);
}
});
}
Ketika layanan timer dijalankan dan mencapai hitungan dua, pengecualian dikirim ke komponen Razor, yang pada gilirannya memicu batas kesalahan untuk menampilkan konten kesalahan <ErrorBoundary>
dalam komponen MainLayout
.
Oh, sayang! Oh, astaga! - George Takei
ASP.NET Core