Share via


ASP.NET Çekirdek Blazor eşitleme bağlamı

Not

Bu, bu makalenin en son sürümü değildir. Geçerli sürüm için bu makalenin .NET 8 sürümüne bakın.

Önemli

Bu bilgiler, ticari olarak piyasaya sürülmeden önce önemli ölçüde değiştirilebilen bir yayın öncesi ürünle ilgilidir. Burada verilen bilgilerle ilgili olarak Microsoft açık veya zımni hiçbir garanti vermez.

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);
    }
}
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;
}
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;
}

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şenin OnNotify yöntemini çağırır. Doğru bağlama geçmek ve işlemeyi kuyruğa almak için InvokeAsync 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öntemi OnNotify 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şenin OnNotify yöntemini Blazor'ın eşitleme bağlamının dışından çağırır. Doğru bağlama geçmek ve işlemeyi kuyruğa almak için InvokeAsync 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öntemi OnNotify 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ığı TaskSendReportiç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 whileTimerService.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>

Hata sınırının yalnızca statik MainLayout bir bileşene uygulandığı Web Apps'teBlazor, 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 bileşenlerin geri kalanının bileşen hiyerarşisinin MainLayout daha aşağısında etkileşime olanak tanımak için, bileşendekiComponents/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-catchzamanlayıcıyı başlatın. Bloğun catch yan tümcesinde try-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 ActionTask.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