Delen via


synchronisatiecontext ASP.NET Core Blazor

Notitie

Dit is niet de nieuwste versie van dit artikel. Zie de .NET 9-versie van dit artikelvoor de huidige release.

Waarschuwing

Deze versie van ASP.NET Core wordt niet meer ondersteund. Zie de .NET- en .NET Core-ondersteuningsbeleidvoor meer informatie. Zie de .NET 9-versie van dit artikelvoor de huidige release.

Belangrijk

Deze informatie heeft betrekking op een pre-releaseproduct dat aanzienlijk kan worden gewijzigd voordat het commercieel wordt uitgebracht. Microsoft geeft geen garanties, uitdrukkelijk of impliciet, met betrekking tot de informatie die hier wordt verstrekt.

Zie de .NET 9-versie van dit artikelvoor de huidige release.

Blazor gebruikt een synchronisatiecontext (SynchronizationContext) om één logische thread van uitvoering af te dwingen. De levenscyclusmethoden van een onderdeel en callbacks van gebeurtenissen die door Blazor worden gegenereerd, worden uitgevoerd in de synchronisatiecontext.

Blazorsynchronisatiecontext aan de serverzijde probeert een omgeving met één thread te emuleren, zodat deze nauw overeenkomt met het WebAssembly-model in de browser, dat één thread bevat. Deze emulatie is alleen gericht op een afzonderlijk circuit, wat betekent dat twee verschillende circuits parallel kunnen worden uitgevoerd. Op een bepaald tijdstip binnen een circuit wordt werk uitgevoerd op precies één thread, wat de indruk van één logische thread oplevert. Er worden geen twee bewerkingen tegelijk uitgevoerd binnen hetzelfde circuit.

Eén logische thread van uitvoering impliceert geen enkele asynchrone controlestroom. Een component is opnieuw betredenbaar op elk punt waar het wacht op een onvolledige Task. levenscyclusmethoden of methoden voor het verwijderen van onderdelen kunnen worden aangeroepen voordat de asynchrone controlestroom wordt hervat nadat een Task is voltooid. Daarom moet een onderdeel ervoor zorgen dat het zich in een geldige staat bevindt voordat het wacht op een mogelijk onvolledige Task. Een onderdeel moet er met name voor zorgen dat het een geldige status heeft voor rendering wanneer OnInitializedAsync of OnParametersSetAsync retourneert. Als een van deze methoden een onvolledige Taskretourneert, moeten ze ervoor zorgen dat het deel van de methode dat synchroon wordt voltooid, de component in een geldige toestand voor weergave achterlaat.

Een andere implicatie van re-entrant onderdelen is dat een methode een Task pas kan uitstellen na de retour van de methode door deze door te geven aan ComponentBase.InvokeAsync. Het aanroepen van ComponentBase.InvokeAsync kan de Task alleen uitstellen totdat de volgende await operator is bereikt.

Onderdelen kunnen IDisposable of IAsyncDisposable implementeren om asynchrone methoden aan te roepen met behulp van een CancellationToken van een CancellationTokenSource die wordt geannuleerd wanneer het onderdeel wordt verwijderd. Dit hangt echter echt af van het scenario. Het is aan de auteur van het onderdeel om te bepalen of dat het juiste gedrag is. Als u bijvoorbeeld een SaveButton-onderdeel implementeert dat bepaalde lokale gegevens opslaat in een database wanneer een knop Opslaan is geselecteerd, is de auteur van het onderdeel misschien inderdaad van plan de wijzigingen te negeren als de gebruiker de knop selecteert en snel naar een andere pagina navigeert, waardoor het onderdeel kan worden verwijderd voordat het asynchrone opslaan is voltooid.

Een wegwerpcomponent kan controleren op verwijdering nadat is gewacht op een Task die de CancellationTokenvan het component niet ontvangt. Onvolledige Task's kunnen ook voorkomen dat de garbage collection van een afgehandeld onderdeel plaatsvindt.

ComponentBase negeert uitzonderingen veroorzaakt door Task annulering (meer precies, het negeert alle uitzonderingen als de verwachte Taskzijn geannuleerd), zodat onderdeelmethoden TaskCanceledException en OperationCanceledExceptionniet hoeven te verwerken.

ComponentBase kan niet aan de voorgaande richtlijnen voldoen, omdat het niet kan conceptualiseren wat een geldige status vormt voor een afgeleid component en zelf geen IDisposable of IAsyncDisposableimplementeert. Als OnInitializedAsync een onvolledige Task retourneert die geen CancellationToken gebruikt en het onderdeel wordt verwijderd voordat het Task is voltooid, roept ComponentBase nog steeds OnParametersSet aan en wacht op OnParametersSetAsync. Als een wegwerponderdeel geen CancellationTokengebruikt, moet OnParametersSet en OnParametersSetAsync controleren of het onderdeel is verwijderd.

Thread-blokkerende aanroepen voorkomen

In het algemeen roept u de volgende methoden niet aan in onderdelen. Met de volgende methoden wordt de uitvoeringsthread geblokkeerd en kan de app dus geen werk hervatten totdat de onderliggende Task is voltooid:

Notitie

Blazor documentatievoorbeelden die gebruikmaken van de thread-blokkerende methoden zoals vermeld in deze sectie, worden alleen gebruikt voor demonstratiedoeleinden en niet voor het geven van aanbevolen coderingsrichtlijnen. Een paar demonstraties van onderdeelcode simuleren bijvoorbeeld een langlopend proces door Thread.Sleepaan te roepen.

Componentmethoden extern aanroepen om de status bij te werken

In het geval dat een onderdeel moet worden bijgewerkt op basis van een externe gebeurtenis, zoals een timer of een andere melding, gebruikt u de methode InvokeAsync, waarmee de uitvoering van code wordt verzonden naar Blazorsynchronisatiecontext. Denk bijvoorbeeld aan de volgende notifier-service die elk luisterend onderdeel kan informeren over de bijgewerkte status. De methode Update kan overal in de app worden aangeroepen.

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

Registreer de diensten.

  • Registreer voor ontwikkeling aan de clientzijde de services als singletons in het Program-bestand aan de clientzijde:

    builder.Services.AddSingleton<NotifierService>();
    builder.Services.AddSingleton<TimerService>();
    
  • Voor ontwikkeling aan de serverzijde registreer je de services als gescopeerd in het Program-bestand van de server.

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

Gebruik de NotifierService om een onderdeel bij te werken.

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() => _ = Task.Run(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;
    }
}

In het voorgaande voorbeeld:

  • De timer wordt gestart buiten de synchronisatiecontext van Blazormet _ = Task.Run(Timer.Start).
  • NotifierService roept de OnNotify methode van het onderdeel aan. InvokeAsync wordt gebruikt om over te schakelen naar de juiste context en een rerender in de wachtrij te zetten. Zie ASP.NET Core Razor component renderingvoor meer informatie.
  • Het onderdeel implementeert IDisposable. De OnNotify delegate wordt afgemeld in de methode Dispose, die wordt aangeroepen door het framework wanneer de component wordt verwijderd. Zie ASP.NET Core Razor componentafhandelingvoor meer informatie.
  • NotifierService roept de OnNotify methode van het onderdeel aan buiten de synchronisatiecontext van Blazor. InvokeAsync wordt gebruikt om over te schakelen naar de juiste context en een rerender in de wachtrij te zetten. Zie ASP.NET Core Razor component renderingvoor meer informatie.
  • Het onderdeel implementeert IDisposable. De OnNotify gedelegeerde wordt afgemeld bij de methode Dispose, die wordt aangeroepen door het framework wanneer het onderdeel wordt verwijderd. Zie ASP.NET Core Razor componentafhandelingvoor meer informatie.

Belangrijk

Als een Razor-onderdeel een gebeurtenis definieert die wordt geactiveerd vanuit een achtergrondthread, kan het onderdeel vereist zijn om de uitvoeringscontext (ExecutionContext) vast te leggen en te herstellen op het moment dat de handler is geregistreerd. Zie Bellen InvokeAsync(StateHasChanged) ervoor zorgt dat pagina terugvalt op de standaardcultuur (dotnet/aspnetcore #28521)voor meer informatie.

Als u gevangen uitzonderingen vanuit de achtergrond TimerService naar het onderdeel wilt doorsturen om de uitzonderingen zoals normale levenscyclusevenement-uitzonderingen te behandelen, raadpleegt u de sectie Gevangen uitzonderingen verwerken buiten de levenscyclus van een Razor onderdeel.

Afgehandelde uitzonderingen buiten de levenscyclus van een Razor-component verwerken

Gebruik ComponentBase.DispatchExceptionAsync in een Razor-onderdeel om uitzonderingen te verwerken die buiten de levenscyclusoproepstack van het onderdeel zijn opgetreden. Hierdoor kan de code van het onderdeel uitzonderingen behandelen alsof het uitzonderingen voor de levenscyclusmethode zijn. Daarna kunnen Blazorfoutafhandelingsmechanismen, zoals foutgrenzen, de uitzonderingen verwerken.

Notitie

ComponentBase.DispatchExceptionAsync wordt gebruikt in Razor onderdeelbestanden (.razor) die overnemen van ComponentBase. Wanneer u onderdelen maakt die implement IComponent directly, gebruikt u RenderHandle.DispatchExceptionAsync.

Als u gevangen uitzonderingen buiten de levenscyclus van een Razor-onderdeel wilt afhandelen, geeft u de uitzondering door aan DispatchExceptionAsync en wacht u op het resultaat:

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

Een veelvoorkomend scenario voor de voorgaande benadering is wanneer een component een asynchrone operatie start, maar niet wacht op een Task, vaak het fire-and-forget patroon genoemd omdat de methode wordt gestart (geactiveerd) en het resultaat van de methode wordt vergeten (weggegooid). Als de bewerking mislukt, wilt u mogelijk dat het onderdeel de fout behandelt als een levenscyclusuitzondering voor onderdelen voor een van de volgende doelen:

  • Plaats het onderdeel in een fouttoestand, bijvoorbeeld om een foutgrens te activeren.
  • Beëindig het circuit als er geen foutgrens is.
  • Activeer dezelfde logboekregistratie voor levenscyclus-uitzonderingen.

In het volgende voorbeeld selecteert de gebruiker de knop Rapport verzenden om een achtergrondmethode te activeren, ReportSender.SendAsync, waarmee een rapport wordt verzonden. In de meeste gevallen wacht een onderdeel op de Task van een asynchrone aanroep en werkt de gebruikersinterface bij om aan te geven dat de bewerking is voltooid. In het volgende voorbeeld wacht de SendReport methode niet op een Task en rapporteert het resultaat niet aan de gebruiker. Omdat het onderdeel de Task opzettelijk weglaat in SendReport, vinden eventuele asynchrone fouten buiten de normale levenscyclusaanroepstack plaats, waardoor ze niet worden opgemerkt door Blazor:

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

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

Als u fouten zoals uitzonderingen voor de levenscyclusmethode wilt behandelen, verzendt u expliciet uitzonderingen terug naar het onderdeel met DispatchExceptionAsync, zoals in het volgende voorbeeld wordt getoond:

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

Een alternatieve benadering maakt gebruik van Task.Run:

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

Voor een werkende demonstratie implementeert u het voorbeeld van de timermelding in Componentmethoden extern aanroepen om de statusbij te werken. Voeg in een Blazor-app de volgende bestanden toe uit het voorbeeld van de timermelding en registreer de services in het Program-bestand, zoals in de sectie wordt uitgelegd:

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

In het voorbeeld wordt een timer buiten de levenscyclus van een Razor onderdeel gebruikt, waarbij een niet-verwerkte uitzondering normaal gesproken niet wordt verwerkt door de mechanismen voor foutafhandeling van Blazor, zoals een foutgrens.

Wijzig eerst de code in TimerService.cs om een kunstmatige uitzondering te maken buiten de levenscyclus van het onderdeel. In de while lus van TimerService.csgenereert u een uitzondering wanneer de elapsedCount een waarde van twee bereikt:

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

Plaats een foutgrens in de hoofdindeling van de app. Vervang de <article>...</article> markering door de volgende markeringen.

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

In Blazor Web App, waarbij de foutgrens alleen wordt toegepast op een statische MainLayout-component, is de grens alleen actief tijdens de statische server-side rendering (SSR) fase. De grens wordt niet geactiveerd omdat een onderdeel verderop in de onderdeelhiërarchie interactief is. Als u interactiviteit breed wilt inschakelen voor het MainLayout-onderdeel en de rest van de onderdelen, schakelt u interactieve rendering in voor de HeadOutlet- en Routes onderdeelexemplaren in het App-onderdeel (Components/App.razor). In het volgende voorbeeld wordt de rendermodus Interactive Server (InteractiveServer) gebruikt:

<HeadOutlet @rendermode="InteractiveServer" />

...

<Routes @rendermode="InteractiveServer" />

Als u de app op dit punt uitvoert, wordt de uitzondering opgeworpen wanneer het verstreken aantal een waarde van twee bereikt. De gebruikersinterface verandert echter niet. De foutgrens geeft de foutinhoud niet weer.

Als u uitzonderingen van de timerservice wilt verzenden naar het Notifications onderdeel, worden de volgende wijzigingen aangebracht in het onderdeel:

  • Start de timer in een try-catch opdracht. In de catch component van het try-catch blok worden uitzonderingen teruggestuurd naar het onderdeel door de Exception door te geven aan DispatchExceptionAsync en te wachten op het resultaat.
  • Start in de StartTimer methode de asynchrone timerservice in de Action delegate van Task.Run en negeer de geretourneerde Taskopzettelijk.

De StartTimer methode van het Notifications onderdeel (Notifications.razor):

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

Wanneer de timerservice een aantal van twee uitvoert en bereikt, wordt de uitzondering verzonden naar het Razor onderdeel, waardoor de foutgrens wordt geactiveerd om de foutinhoud van de <ErrorBoundary> weer te geven in het MainLayout-onderdeel:

Oh, lieve! Oh, mijn! - George Takei