ASP.NET Çekirdek Blazor eşitleme bağlamı
Uyarı
ASP.NET Core'un bu sürümü artık desteklenmiyor. Daha fazla bilgi için bkz . .NET ve .NET Core Destek İlkesi. Geçerli sürüm için bu makalenin .NET 8 sürümüne bakın.
Blazor, tek bir mantıksal yürütme iş akışını zorlayan bir eşitleme bağlamı (SynchronizationContext) kullanır. Bileşenin yaşam döngüsü yöntemleri ve Blazor tarafından oluşturulan olay geri çağırmaları eşitleme bağlamında yürütülür.
Blazor'nin sunucu tarafı eşitleme bağlamı, tek iş parçacıklı bir ortamı tarayıcıdaki WebAssembly modeliyle yakından eşleşecek şekilde öykünmeye çalışır. Bu öykünme yalnızca tek bir bağlantı hattıyla sınırlıdır, yani iki farklı devre paralel olarak çalıştırılabilir. Bir bağlantı hattı içinde belirli bir zamanda, iş tam olarak bir iş parçacığı üzerinde gerçekleştirilir ve bu da tek bir mantıksal iş parçacığının izlenimini verir. İki işlem aynı devre içinde eşzamanlı olarak yürütülür.
İş parçacığını engelleyen çağrıları önleme
Genel olarak bileşenlerde aşağıdaki yöntemleri çağırmayın. Aşağıdaki yöntemler yürütme iş parçacığını engeller ve dolayısıyla temel Task tamamlanana kadar uygulamanın çalışmayı sürdürmesini de engeller:
Not
Blazor belgelerinde yer alan ve bu bölümde açıklanan iş parçacığı engelleme yöntemlerini kullanan örnekler, yöntemleri önerilen kodlama rehberi olarak değil yalnızca tanıtım amaçlı kullanmaktadır. Örneğin birkaç bileşen kodu tanıtımı Thread.Sleep yöntemini çağırarak uzun süre çalışan bir işleme öykünür.
Durumu güncelleştirmek için bileşen yöntemlerini dışarıdan çağırma
Bileşenin bir dış olay (zamanlayıcı veya başka bir bildirim gibi) temelinde güncelleştirilmesi gereken durumlarda, kod yürütmeyi Blazor'ın eşitleme bağlamına dağıtan InvokeAsync
yöntemini kullanın. Örneğin dinleyen tüm bileşenlere güncelleştirilmiş durumu bildirebilecek aşağıdaki bildirimci hizmetini inceleyin. Update
yöntemi uygulamanın herhangi bir yerinden çağrılabilir.
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}");
}
}
}
}
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}");
}
}
}
}
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}");
}
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}");
}
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;
}
Hizmetleri kaydetme:
İstemci tarafı geliştirme için, hizmetleri istemci tarafı
Program
dosyasına tekil olarak kaydedin:builder.Services.AddSingleton<NotifierService>(); builder.Services.AddSingleton<TimerService>();
Sunucu tarafı geliştirme için, hizmetleri sunucu
Program
dosyasında kapsamı belirlenmiş olarak kaydedin:builder.Services.AddScoped<NotifierService>(); builder.Services.AddScoped<TimerService>();
Bileşeni güncelleştirmek için NotifierService
kullanın.
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;
}
}
Yukarıdaki örnekte:
- Zamanlayıcı ile eşitleme bağlamının Blazor
_ = Task.Run(Timer.Start)
dışında başlatılır. NotifierService
bileşeninOnNotify
yöntemini çağırır.InvokeAsync
, doğru bağlama geçmek ve bir rerender'ı sıralamak için kullanılır. Daha fazla bilgi için bkz. ASP.NET Core Razor bileşenini işleme.- Bileşen IDisposable gerçekleştirir. Bileşen atılırken çerçeve tarafından çağrılan
Dispose
yöntemiOnNotify
temsilcisinin aboneliği kaldırılır. Daha fazla bilgi için bkz. ASP.NET Core Razor bileşeni yaşam döngüsü.
NotifierService
, bileşeninOnNotify
yöntemini Blazor'ın eşitleme bağlamının dışından çağırır.InvokeAsync
, doğru bağlama geçmek ve bir rerender'ı sıralamak için kullanılır. Daha fazla bilgi için bkz. ASP.NET Core Razor bileşenini işleme.- Bileşen IDisposable gerçekleştirir. Bileşen atılırken çerçeve tarafından çağrılan
Dispose
yöntemiOnNotify
temsilcisinin aboneliği kaldırılır. Daha fazla bilgi için bkz. ASP.NET Core Razor bileşeni yaşam döngüsü.
Önemli
Bir Razor bileşen arka plan iş parçacığından tetiklenen bir olayı tanımlıyorsa, işleyici kaydedildiğinde yürütme bağlamını (ExecutionContext) yakalamak ve geri yüklemek için bileşen gerekebilir. Daha fazla bilgi için bkz . Çağrı InvokeAsync(StateHasChanged)
, sayfayı varsayılan kültüre geri döndürmeye neden oluyor (dotnet/aspnetcore #28521).
Normal yaşam döngüsü olayı özel durumları gibi özel durumları işlemek üzere yakalanan özel durumları arka planda TimerService
bileşene göndermek için, bileşenin Razor yaşam döngüsü dışında yakalanan özel durumları işleme bölümüne bakın.
Bir bileşenin Razor yaşam döngüsü dışında yakalanan özel durumları işleme
Bileşenin yaşam döngüsü çağrı yığını dışında oluşan özel durumları işlemek için bir Razor bileşende kullanınComponentBase.DispatchExceptionAsync. Bu, bileşenin kodunun özel durumları yaşam döngüsü yöntemi özel durumları gibi işlemesine izin verir. Bundan sonra hata Blazorsınırları gibi hata işleme mekanizmaları özel durumları işleyebilir.
Not
ComponentBase.DispatchExceptionAsync, öğesinden ComponentBasedevralan bileşen dosyalarında Razor (.razor
) kullanılır. kullanan bileşenleri implement IComponent directlyRenderHandle.DispatchExceptionAsyncoluştururken.
Bir Razor bileşenin yaşam döngüsü dışında yakalanan özel durumları işlemek için özel durumu öğesine DispatchExceptionAsync geçirin ve sonucu bekleyelim:
try
{
...
}
catch (Exception ex)
{
await DispatchExceptionAsync(ex);
}
Önceki yaklaşım için yaygın bir senaryo, bir bileşenin zaman uyumsuz bir işlem başlatması ancak genellikle yangın ve unut deseni olarak adlandırılan bir Tasköğesini beklememesidir çünkü yöntem tetiklenir (başlatılır) ve yöntemin sonucu unutulur (atılır). İşlem başarısız olursa, bileşenin aşağıdaki hedeflerden herhangi biri için hatayı bileşen yaşam döngüsü özel durumu olarak işlemesini isteyebilirsiniz:
- Hata sınırını tetikleme gibi bir hata durumuna bileşeni yerleştirin.
- Hata sınırı yoksa devreyi sonlandırın.
- Yaşam döngüsü özel durumları için aynı günlüğü tetikleyin.
Aşağıdaki örnekte kullanıcı Rapor gönder düğmesini seçerek rapor gönderen bir arka plan yöntemi ( ReportSender.SendAsync
, ) tetikler. Çoğu durumda, bir bileşen zaman uyumsuz bir çağrı bekler Task ve işlemin tamamlandığını belirtmek için kullanıcı arabirimini güncelleştirir. Aşağıdaki örnekte SendReport
yöntemi bir öğesini beklemez Task ve sonucu kullanıcıya bildirmez. Bileşen içinde öğesini kasıtlı olarak attığı Task SendReport
için, zaman uyumsuz hatalar normal yaşam döngüsü çağrı yığınından oluşur ve bu nedenle tarafından Blazorgörülmez:
<button @onclick="SendReport">Send report</button>
@code {
private void SendReport()
{
_ = ReportSender.SendAsync();
}
}
Yaşam döngüsü yöntemi özel durumları gibi hataları işlemek için, aşağıdaki örnekte gösterildiği gibi özel durumları ile bileşenine DispatchExceptionAsyncaçıkça geri gönderin:
<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);
}
}
}
Alternatif bir yaklaşımdan yararlanan:Task.Run
private void SendReport()
{
_ = Task.Run(async () =>
{
try
{
await ReportSender.SendAsync();
}
catch (Exception ex)
{
await DispatchExceptionAsync(ex);
}
});
}
Çalışan bir gösterim için, Durumu güncelleştirmek için bileşen yöntemlerini harici olarak çağırma bölümünde zamanlayıcı bildirimi örneğini uygulayın. Bir Blazor uygulamada zamanlayıcı bildirim örneğinden aşağıdaki dosyaları ekleyin ve bölümünde açıklandığı gibi hizmetleri Program
dosyaya kaydedin:
TimerService.cs
NotifierService.cs
Notifications.razor
Örnek, bileşenin yaşam döngüsünün dışında bir Razor zamanlayıcı kullanır ve burada işlenmeyen özel durum normalde hata sınırı gibi hata işleme mekanizmaları tarafından Blazorişlenmez.
İlk olarak, bileşenin yaşam döngüsünün dışında yapay bir özel durum oluşturmak için içindeki TimerService.cs
kodu değiştirin. döngüsünde while
TimerService.cs
, iki değerine ulaştığında bir özel durum elapsedCount
oluşturur:
if (elapsedCount == 2)
{
throw new Exception("I threw an exception! Somebody help me!");
}
Uygulamanın ana düzenine bir hata sınırı yerleştirin. İşaretlemeyi <article>...</article>
aşağıdaki işaretlemeyle değiştirin.
MainLayout.razor
içinde:
<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>
S'de Blazor Web Apphata sınırı yalnızca statik MainLayout
bir bileşene uygulanırken, sınır yalnızca statik sunucu tarafı işleme (statik SSR) aşamasında etkindir. Sınır, yalnızca bileşen hiyerarşisinin daha aşağısındaki bir bileşen etkileşimli olduğundan etkinleştirilmez. Bileşenin ve rest bileşenlerin bileşen hiyerarşisinin daha aşağısında etkileşime geniş bir şekilde MainLayout
olanak tanımak için, bileşendeki (Components/App.razor
) ve Routes
bileşen örnekleri için HeadOutlet
etkileşimli işlemeyi App
etkinleştirin. Aşağıdaki örnek Etkileşimli Sunucu (InteractiveServer
) işleme modunu benimser:
<HeadOutlet @rendermode="InteractiveServer" />
...
<Routes @rendermode="InteractiveServer" />
Uygulamayı bu noktada çalıştırırsanız, geçen sayı iki değerine ulaştığında özel durum oluşturulur. Ancak kullanıcı arabirimi değişmez. Hata sınırı hata içeriğini göstermez.
Zamanlayıcı hizmetinden bileşene özel durumlar göndermek için Notifications
, bileşende aşağıdaki değişiklikler yapılır:
- Deyiminde
try-catch
zamanlayıcıyı başlatın. Bloğuncatch
yan tümcesindetry-catch
, özel durumlar öğesine geçirilerek Exception ve sonucu beklenerek bileşene DispatchExceptionAsync geri gönderilir. - yönteminde
StartTimer
, zaman uyumsuz zamanlayıcı hizmetini temsilcisinde Action Task.Run başlatın ve döndürülen Tasköğesini kasıtlı olarak atın.
StartTimer
Bileşenin Notifications
yöntemi (Notifications.razor
):
private void StartTimer()
{
_ = Task.Run(async () =>
{
try
{
await Timer.Start();
}
catch (Exception ex)
{
await DispatchExceptionAsync(ex);
}
});
}
Zamanlayıcı hizmeti yürütülür ve iki sayısına ulaştığında, özel durum bileşene Razor gönderilir ve bu da bileşenin hata içeriğini <ErrorBoundary>
MainLayout
görüntülemek için hata sınırını tetikler:
Ah, canım! Benim! - George Takei
ASP.NET Core