Megosztás a következőn keresztül:


ASP.NET Core Blazor szinkronizálási környezete

Jegyzet

Ez nem a cikk legújabb verziója. Az aktuális kiadásról a cikk .NET 10-es verziójában olvashat.

Figyelmeztetés

A ASP.NET Core ezen verziója már nem támogatott. További információ: .NET és .NET Core támogatási szabályzat. Az aktuális kiadást a jelen cikk .NET 9-es verziójában.

Blazor egy szinkronizálási környezetet (SynchronizationContext) használ egyetlen logikai végrehajtási szál kényszerítéséhez. Az összetevők életciklus-metódusai és az Blazor által kiváltott eseményvisszahívások végrehajtása a szinkronizálási környezetben történik.

Blazorkiszolgálóoldali szinkronizálási környezet egy egyszálas környezetet próbál emulálni, hogy az szorosan megfeleljen a böngésző WebAssembly modelljének, amely egyszálas. Ez az emuláció csak egy adott kapcsolatcsoportra terjed ki, ami azt jelenti, hogy két különböző kapcsolatcsoport futhat párhuzamosan. A kapcsolatcsoporton belül bármely adott időpontban pontosan egy szálon végzik a munkát, ami egyetlen logikai szál benyomását kelti. Egyetlen művelet sem fut egyszerre ugyanabban a kapcsolatcsoportban.

Egyetlen logikai végrehajtási szál nem jelent egyetlen aszinkron vezérlőfolyamatot. Az összetevő bármely ponton újból beléphet, ahol egy nem teljes Task-ra várakozik. Életciklus-metódusok vagy összetevő-törlési módszerek meghívhatók, mielőtt az aszinkron vezérlési folyamat újraindul, miután megvárjuk a Task befejeződését. Ezért egy összetevőnek meg kell győződnie arról, hogy érvényes állapotban van, mielőtt várakozna egy potenciálisan hiányos Task-ra. Különösen fontos, hogy egy komponens biztosítsa, hogy érvényes állapotban legyen a rendereléshez, amikor OnInitializedAsync vagy OnParametersSetAsync visszatér. Ha bármelyik metódus hiányos Taskad vissza, gondoskodnia kell arról, hogy a módszer szinkron módon befejezett része érvényes állapotban hagyja az összetevőt a rendereléshez.

Az újrakezdő összetevők egy másik következménye, hogy egy metódus nem késleltethet egy Task-t azáltal, hogy azt átadja ComponentBase.InvokeAsync-nek, amíg a metódus vissza nem tér. A ComponentBase.InvokeAsync hívása csak a következő Task eléréséig halaszthatja a await.

Az összetevők implementálhatják a IDisposable vagy IAsyncDisposable, hogy aszinkron metódusokat hívjanak meg egy CancellationToken segítségével, ami a komponens eltávolításakor megszakadó CancellationTokenSource-ból származik. Ez azonban valóban a forgatókönyvtől függ. Az összetevő szerzője határozza meg, hogy ez-e a helyes viselkedés. Ha például olyan SaveButton összetevőt implementál, amely egy mentési gomb kiválasztásakor megőrzi a helyi adatokat egy adatbázisban, az összetevő szerzője valóban elvetheti a módosításokat, ha a felhasználó kiválasztja a gombot, és gyorsan egy másik oldalra navigál, amely az összetevőt az aszinkron mentés befejezése előtt megsemmisítheti.

Egy eldobható összetevő ellenőrizheti az ártalmatlanítást, miután bármely Task-ra várt, amely nem kapja meg a komponens CancellationToken-jét. A hiányos Taskis megakadályozhatja a hulladékgyűjtést egy megsemmisített összetevő esetében.

ComponentBase figyelmen kívül hagyja a Task lemondása által okozott kivételeket (pontosabban figyelmen kívül hagyja kivételeket, ha a várt Taskmegszakítják), így az összetevők metódusainak nem kell kezelnie TaskCanceledException és OperationCanceledException.

ComponentBase nem tudja követni az előző irányelveket, mert nem koncepcializálja, hogy mi számít érvényes állapotnak egy származtatott összetevőhöz, és nem valósít meg IDisposable vagy IAsyncDisposable. Ha OnInitializedAsync olyan hiányos Task-et ad vissza, amely nem használ egy CancellationToken-t, és az összetevőt a Task befejeződése előtt megsemmisítik, ComponentBase továbbra is meghívja OnParametersSet-öt, és meg kell várnia OnParametersSetAsync-ot. Ha egy eldobható összetevő nem használja a CancellationToken-t, akkor a OnParametersSet-nek és OnParametersSetAsync-nek ellenőrizniük kell, hogy az összetevő meg lett-e semmisítve.

A szálblokkoló hívások elkerülése

Általában ne hívja meg a következő metódusokat az összetevőkben. A következő metódusok blokkolják a végrehajtási szálat, így az alkalmazás nem tudja folytatni a munkát, amíg a Task nem fejeződik be.

Jegyzet

Blazor dokumentációs példák, amelyek az ebben a szakaszban említett szálblokkoló módszereket használják, csak bemutató célokra használják a metódusokat, nem ajánlott kódolási útmutatóként. Néhány összetevőkód-bemutató például egy hosszú ideig futó folyamatot szimulál Thread.Sleepmeghívásával.

Külső összetevőmetelyek meghívása az állapot frissítéséhez

Abban az esetben, ha egy összetevőt külső esemény , például időzítő vagy egyéb értesítés alapján kell frissíteni, használja a InvokeAsync metódust, amely kódfuttatást küld Blazorszinkronizálási környezetének. Vegyük például az alábbi értesítési szolgáltatás, amely képes értesíteni a figyelési összetevőket a frissített állapotról. A Update metódus az alkalmazás bármely pontjáról meghívható.

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

A szolgáltatások regisztrálása:

  • Ügyféloldali fejlesztés esetén regisztrálja a szolgáltatásokat önállóan az ügyféloldali Program fájlban:

    builder.Services.AddSingleton<NotifierService>();
    builder.Services.AddSingleton<TimerService>();
    
  • A kiszolgálóoldali fejlesztéshez regisztrálja a szolgáltatásokat a kiszolgáló Program fájlban hatókörként:

    builder.Services.AddScoped<NotifierService>();
    builder.Services.AddScoped<TimerService>();
    

Az összetevő frissítéséhez használja a 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;
    }
}

Az előző példában:

  • Az időzítő Blazor_ = Task.Run(Timer.Start)szinkronizálási környezetén kívül van elindítva.
  • NotifierService meghívja az összetevő OnNotify metódusát. InvokeAsync a megfelelő környezetre való váltáshoz és egy rerender létrehozásához használható. További információkért lásd: ASP.NET Core Razor összetevő renderelése.
  • Az összetevő implementálja a IDisposable. A OnNotify delegált le van iratkozva a Dispose metódusban, amelyet a keretrendszer hív meg, amikor az összetevőt ártalmatlanítják. További információért lásd: ASP.NET Core Razor összetevő megsemmisítés.
  • NotifierService meghívja az összetevő OnNotify metódusát Blazorszinkronizálási környezetén kívül. InvokeAsync a megfelelő környezetre való váltáshoz és egy rerender létrehozásához használható. További információkért lásd: ASP.NET Core Razor összetevő renderelése.
  • Az összetevő implementálja a IDisposable. A OnNotify delegált le van iratkozva a Dispose metódusban, amelyet a keretrendszer hív meg, amikor az összetevőt ártalmatlanítják. További információért lásd: ASP.NET Core Razor összetevő megsemmisítés.

Fontos

Ha egy Razor összetevő egy háttérszálból aktivált eseményt határoz meg, előfordulhat, hogy az összetevőnek a kezelő regisztrálásakor rögzítenie kell és vissza kell állítania a végrehajtási környezetet (ExecutionContext). További információért lásd: A InvokeAsync(StateHasChanged) hívása miatt a lap visszatér az alapértelmezett kultúrához (dotnet/aspnetcore #28521).

Ha a kifogott kivételeket a háttérből TimerService az összetevőre szeretné küldeni, hogy a kivételeket a normál életciklus-esemény kivételeiként kezelje, tekintse meg a Az Razor-összetevő életciklus- szakaszán kívüli kifogott kivételek kezelése című témakört.

Az Razor-összetevők életciklusán kívül észlelt kivételek kezelése

Egy ComponentBase.DispatchExceptionAsync-összetevő Razor használva feldolgozhatja az összetevő életciklus-hívásveremén kívül eső kivételeket. Ez lehetővé teszi, hogy az összetevő kódja úgy kezelje a kivételeket, mintha életciklus-metódus kivételei lennének. Ezt követően Blazorhibakezelési mechanizmusai, például hibahatárok, feldolgozhatják a kivételeket.

Jegyzet

ComponentBase.DispatchExceptionAsync Razor .razoröröklő összetevőfájlokban (ComponentBase) használatos. Amikor implement IComponent directlyösszetevőket hoz létre, használja a RenderHandle.DispatchExceptionAsync.

Ha egy Razor összetevő életciklusán kívüli kivételeket szeretne kezelni, adja át a kivételt DispatchExceptionAsync, és várja meg az eredményt:

try
{
    ...
}
catch (Exception ex)
{
    await DispatchExceptionAsync(ex);
}

Az előző megközelítés gyakori forgatókönyve, hogy egy összetevő aszinkron műveletet indít el, de nem vár egy Task, amelyet gyakran tűznek neveznek, és elfelejti mintát, mert a metódus aktiválódik (elindítva), és a metódus eredménye elfelejtett (eldobva). Ha a művelet meghiúsul, előfordulhat, hogy az összetevő az alábbi célok bármelyike esetén összetevő-életciklus-kivételként kezeli a hibát:

  • Helyezze az összetevőt hibás állapotba, például egy hibahatáraktiválásához.
  • Ha nincs hibahatár, állítsa le az áramkört.
  • Aktiválja ugyanazt a naplózást, amely az életciklus-kivételeknél fordul elő.

A következő példában a felhasználó kiválasztja a Jelentés küldése gombot egy olyan háttérmetódus aktiválásához, ReportSender.SendAsync, amely jelentést küld. A legtöbb esetben egy összetevő az aszinkron hívás Task eseményének megérkezésére vár, és frissíti a felhasználói felületet, hogy jelezze a művelet befejezését. Az alábbi példában a SendReport metódus nem várja meg a Task, és nem jelenti az eredményt a felhasználónak. Mivel az összetevő szándékosan elveti a Task-t a SendReport-ben, bármilyen aszinkron hiba a normál életciklus-hívásveremen kívül történik, ezért a Blazornem látja:

<button @onclick="SendReport">Send report</button>

@code {
    private void SendReport()
    {
        _ = ReportSender.SendAsync();
    }
}

Az életciklus-metóduskivételekhez hasonló hibák kezeléséhez explicit módon küldje vissza a kivételeket az összetevőnek DispatchExceptionAsync, ahogy az alábbi példa is mutatja:

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

Egy alternatív megközelítés a Task.Runhasználata:

private void SendReport()
{
    _ = Task.Run(async () =>
    {
        try
        {
            await ReportSender.SendAsync();
        }
        catch (Exception ex)
        {
            await DispatchExceptionAsync(ex);
        }
    });
}

Egy működő bemutatóhoz implementálja az időzítő értesítési példáját Külső összetevő metódusainak meghívása az állapotfrissítéséhez. Egy Blazor alkalmazásban adja hozzá a következő fájlokat az időzítő értesítési példájából, és regisztrálja a szolgáltatásokat a Program fájlban, ahogy a szakasz ismerteti:

  • TimerService.cs
  • NotifierService.cs
  • Notifications.razor

A példa egy Razor összetevő életciklusán kívüli időzítőt használ, ahol a kezeletlen kivételeket általában nem dolgozzák fel Blazorhibakezelési mechanizmusai, például egy hibahatár.

Először módosítsa a TimerService.cs kódját, hogy mesterséges kivételt hozzon létre az összetevő életciklusán kívül. A whileTimerService.cs ciklusában kivételt kell kivenni, ha a elapsedCount eléri a két értéket:

if (elapsedCount == 2)
{
    throw new Exception("I threw an exception! Somebody help me!");
}

Helyezzen egy hibahatárt az alkalmazás fő elrendezésében. Cserélje le a <article>...</article> jelölést a következő jelölésre.

A 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>

Azon Blazor Web Apps esetében, amelynél a hibahatár csak statikus MainLayout összetevőre van alkalmazva, a határ csak a statikus kiszolgálóoldali renderelés (statikus SSR) fázisában aktív. Azért nem aktiválódik a határ, mert egy hierarchiában lejjebb található összetevő interaktív. Az MainLayout összetevő és a többi összetevő interaktivitásának általános engedélyezéséhez engedélyezze az interaktív renderelést az HeadOutlet összetevő (Routes) App és Components/App.razor összetevőpéldányai számára. Az alábbi példa az Interaktív kiszolgáló (InteractiveServer) renderelési módot alkalmazza:

<HeadOutlet @rendermode="InteractiveServer" />

...

<Routes @rendermode="InteractiveServer" />

Ha ezen a ponton futtatja az alkalmazást, a kivétel akkor jelenik meg, amikor az eltelt szám eléri a két értéket. A felhasználói felület azonban nem változik. A hibahatár nem jeleníti meg a hibatartalmat.

Az időzítőszolgáltatás kivételeinek a Notifications összetevőre való visszaküldéséhez a következő módosítások történnek az összetevőn:

  • Indítsa el az időzítőt egy try-catch utasításban. A catch blokk try-catch záradékában a kivételeket úgy küldik vissza az összetevőnek, hogy átadják a Exception-t a DispatchExceptionAsync-nak/nek, és várják az eredményt.
  • A StartTimer metódusban indítsa el az aszinkron időzítő szolgáltatást a ActionTask.Run delegáltjában, majd szándékosan dobja el a visszaadott Task-at.

A StartTimer összetevő (Notifications) Notifications.razor metódusa:

private void StartTimer()
{
    _ = Task.Run(async () =>
    {
        try
        {
            await Timer.Start();
        }
        catch (Exception ex)
        {
            await DispatchExceptionAsync(ex);
        }
    });
}

Amikor az időzítőszolgáltatás végrehajtja és eléri a kettő számát, a kivételt a rendszer a Razor összetevőnek küldi el, amely a hibahatárt aktiválva megjeleníti a <ErrorBoundary> hibatartalmat a MainLayout összetevőben:

Ó, kedvesem! Ó, jaj! - George Takei