Dela via


ASP.NET Core Blazor synkroniseringskontext

Notera

Det här är inte den senaste versionen av den här artikeln. Den aktuella versionen av den här artikeln finns i .NET 9-versionen .

Varning

Den här versionen av ASP.NET Core stöds inte längre. Mer information finns i .NET och .NET Core Support Policy. För den aktuella utgåvan, se .NET 9-versionen av den här artikeln .

Viktig

Den här informationen gäller en förhandsversionsprodukt som kan ändras avsevärt innan den släpps kommersiellt. Microsoft lämnar inga garantier, uttryckliga eller underförstådda, med avseende på den information som tillhandahålls här.

Den aktuella versionen finns i den .NET 9-versionen av den här artikeln.

Blazor använder en synkroniseringskontext (SynchronizationContext) för att säkerställa en enda logisk exekveringstråd. En komponents livscykelmetoder och händelseåteranrop som genereras av Blazor körs i synkroniseringskontexten.

Blazorsynkroniseringskontexten på serversidan försöker emulera en entrådad miljö så att den matchar WebAssembly-modellen i webbläsaren, som är enkeltrådad. Den här emuleringen är endast begränsad till en enskild krets, vilket innebär att två olika kretsar kan köras parallellt. Vid en viss tidpunkt i en krets utförs arbete på exakt en tråd, vilket ger intryck av en enda logisk tråd. Inga två operationer utförs samtidigt i samma krets.

En enda logisk körningstråd innebär inte ett enda asynkront kontrollflöde. En komponent är återinträdande vid varje punkt där den väntar på en ofullständig Task. Livscykelmetoder eller metoder för bortskaffande av komponenter kan anropas innan det asynkrona kontrollflödet återupptas efter att ha väntat på att en Task ska slutföras. Därför måste en komponent se till att den är i ett giltigt tillstånd innan den väntar på en potentiellt ofullständig Task. I synnerhet måste en komponent se till att den är i ett giltigt tillstånd för återgivning när OnInitializedAsync eller OnParametersSetAsync returnerar. Om någon av dessa metoder returnerar en ofullständig Taskmåste de se till att den del av metoden som slutförs synkront lämnar komponenten i ett giltigt tillstånd för återgivning.

En annan implikation av återanvändbara komponenter är att en metod inte kan skjuta upp en Task till efter att metoden har returnerat genom att vidarebefordra den till ComponentBase.InvokeAsync. Att anropa ComponentBase.InvokeAsync kan bara skjuta upp Task tills nästa await operator har nåtts.

Komponenter kan implementera IDisposable eller IAsyncDisposable för att anropa asynkrona metoder med hjälp av en CancellationToken från en CancellationTokenSource som avbryts när komponenten tas bort. Detta beror dock verkligen på scenariot. Det är upp till komponentförfattaren att avgöra om det är rätt beteende. Om du till exempel implementerar en SaveButton komponent som bevarar vissa lokala data till en databas när en spara-knapp har valts, kan komponentförfattaren verkligen vilja ignorera ändringarna om användaren väljer knappen och snabbt navigerar till en annan sida, som kan ta bort komponenten innan den asynkrona sparande slutförs.

En bortkastningsbar komponent kan kontrollera om den har avyttrats efter att ha väntat på någon Task som inte tar emot komponentens CancellationToken. Ofullständiga Tasks kan också förhindra skräpinsamling av en borttagen komponent.

ComponentBase ignorerar undantag som orsakas av Task annullering (mer exakt ignorerar den alla undantag om de väntande Taskavbryts), så komponentmetoderna behöver inte hantera TaskCanceledException och OperationCanceledException.

ComponentBase kan inte följa de föregående riktlinjerna eftersom det inte konceptualiserar vad som utgör ett giltigt tillstånd för en härledd komponent och inte själv implementerar IDisposable eller IAsyncDisposable. Om OnInitializedAsync returnerar en ofullständig Task som inte använder en CancellationToken och komponenten tas bort innan Task slutförs anropar ComponentBase fortfarande OnParametersSet och väntar på OnParametersSetAsync. Om en engångskomponent inte använder en CancellationTokenbör OnParametersSet och OnParametersSetAsync kontrollera om komponenten tas bort.

Undvik trådblockeringsanrop

Anropa vanligtvis inte följande metoder i komponenter. Följande metoder blockerar exekveringstråden och därmed hindrar de appen från att fortsätta arbetet tills den underliggande processen Task har slutförts.

Notera

Blazor dokumentationsexempel som använder de trådblockeringsmetoder som nämns i det här avsnittet använder endast metoderna i demonstrationssyfte, inte som rekommenderad kodningsvägledning. Några komponentkoddemonstrationer simulerar till exempel en tidskrävande process genom att anropa Thread.Sleep.

Anropa komponentmetoder externt för att uppdatera tillståndet

Om en komponent måste uppdateras baserat på en extern händelse, till exempel en timer eller ett annat meddelande, använder du metoden InvokeAsync som skickar kodkörning till Blazorsynkroniseringskontext. Tänk dig till exempel följande meddelandetjänst som kan meddela alla lyssningskomponenter om uppdaterat tillstånd. Metoden Update kan anropas var som helst i appen.

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

Registrera tjänsterna:

  • För utveckling på klientsidan registrerar du tjänsterna som singletons i Program-filen på klientsidan:

    builder.Services.AddSingleton<NotifierService>();
    builder.Services.AddSingleton<TimerService>();
    
  • För utveckling på serversidan registrerar du tjänsterna som begränsade i Program-filen:

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

Använd NotifierService för att uppdatera en komponent.

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

I föregående exempel:

  • Timern startas utanför Blazor:s synkroniseringskontextområde med _ = Task.Run(Timer.Start).
  • NotifierService anropar komponentens OnNotify-metod. InvokeAsync används för att växla till rätt kontext och ange en rerender. För mer information, se ASP.NET Core Razor komponentåtergivning.
  • Komponenten implementerar IDisposable. Delegeringen OnNotify avregistreras i metoden Dispose, som anropas av frameworket när komponenten tas bort. Mer information finns i ASP.NET Core Razor komponenthantering.
  • NotifierService anropar komponentens OnNotify-metod utanför Blazorsynkroniseringskontext. InvokeAsync används för att växla till rätt kontext och ange en rerender. Mer information finns i ASP.NET Core Razor komponentrendering.
  • Komponenten implementerar IDisposable. Det OnNotify ombudet avregistreras i metoden Dispose, som anropas av ramverket när komponenten tas bort. Mer information finns i ASP.NET Core Razor komponenthantering.

Viktig

Om en Razor komponent definierar en händelse som utlöses från en bakgrundstråd kan komponenten krävas för att samla in och återställa körningskontexten (ExecutionContext) när hanteraren registreras. För mer information, se Anropa InvokeAsync(StateHasChanged) orsakar att sidan återställs till standardkulturen (dotnet/aspnetcore #28521).

För mer information om hur du hanterar fångade undantag från bakgrunden TimerService till komponenten för att behandla dem på samma sätt som vanliga livscykelhändelseundantag, se avsnittet Hantera fångade undantag utanför en Razor komponents livscykel.

Hantera fångade undantag utanför en Razor-komponentens livscykel

Använd ComponentBase.DispatchExceptionAsync i en Razor komponent för att bearbeta undantag som genereras utanför komponentens livscykelanropsstack. Detta gör att komponentens kod kan behandla undantag som om de är undantag för livscykelmetoder. Därefter kan Blazorfelhanteringsmekanismer, till exempel felgränser, bearbeta undantagen.

Obs

ComponentBase.DispatchExceptionAsync används i Razor komponentfiler (.razor) som ärver från ComponentBase. När du skapar komponenter som implement IComponent directlyanvänder du RenderHandle.DispatchExceptionAsync.

Om du vill hantera undantag utanför en Razor komponents livscykel skickar du undantaget till DispatchExceptionAsync och väntar på resultatet:

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

Ett vanligt scenario för föregående metod är när en komponent startar en asynkron åtgärd men inte väntar på en Task, vilket ofta kallas för skjut och glöm mönstret eftersom metoden utlöses (startad) och resultatet av metoden glöms bort (kastas bort). Om åtgärden misslyckas kanske du vill att komponenten ska behandla felet som ett undantag för komponentens livscykel för något av följande mål:

  • Placera komponenten i ett felaktigt tillstånd, till exempel för att utlösa en felgräns.
  • Avsluta kretsen om det inte finns någon felgräns.
  • Utlös samma loggning som inträffar för livscykelundantag.

I följande exempel väljer användaren knappen Skicka rapport för att utlösa en bakgrundsmetod, ReportSender.SendAsync, som skickar en rapport. I de flesta fall väntar en komponent på Task för ett asynkront anrop och uppdaterar användargränssnittet för att indikera att åtgärden har slutförts. I följande exempel väntar inte SendReport-metoden på en Task och rapporterar inte resultatet till användaren. Eftersom komponenten avsiktligt tar bort Task i SendReport, uppstår eventuella asynkrona fel utanför den normala livscykelanropsstacken, och därför visas inte av Blazor:

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

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

Om du vill behandla fel som undantag för livscykelmetoder skickar du uttryckligen undantag tillbaka till komponenten med DispatchExceptionAsync, vilket visas i följande exempel:

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

En alternativ metod utnyttjar Task.Run:

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

För en fungerande demonstration implementerar du exemplet med timernviseringar i Anropa komponentmetoder externt för att uppdatera tillståndet. I en Blazor app lägger du till följande filer från timerns meddelandeexempel och registrerar tjänsterna i Program-filen som beskrivs i avsnittet:

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

I exemplet används en timer utanför en Razor komponents livscykel, där ett ohanterat undantag normalt inte bearbetas av Blazorfelhanteringsmekanismer, till exempel en felgräns.

Ändra först koden i TimerService.cs för att skapa ett artificiellt undantag utanför komponentens livscykel. I while-loopen i TimerService.csutlöser du ett undantag när elapsedCount når värdet två:

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

Placera en felgräns i appens huvudlayout. Ersätt <article>...</article>-markering med följande markering.

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

I Blazor Web Appmed felgränsen som endast tillämpas på en statisk MainLayout komponent är gränsen endast aktiv under den statiska återgivningsfasen på serversidan (statisk SSR). Gränsen aktiveras inte bara för att en komponent längre ned i komponenthierarkin är interaktiv. För att möjliggöra interaktivitet brett för MainLayout-komponenten och resten av komponenterna längre ned i komponenthierarkin, aktivera interaktiv återgivning för HeadOutlet- och Routes-komponentinstanserna i App-komponenten (Components/App.razor). I följande exempel används återgivningsläget Interaktiv server (InteractiveServer) :

<HeadOutlet @rendermode="InteractiveServer" />

...

<Routes @rendermode="InteractiveServer" />

Om du kör appen vid den här tidpunkten genereras undantaget när det förflutna antalet når värdet två. Användargränssnittet ändras dock inte. Felgränsen visar inte felinnehållet.

Om du vill skicka undantag från timertjänsten tillbaka till komponenten Notifications görs följande ändringar i komponenten:

StartTimer-metoden för komponenten Notifications (Notifications.razor):

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

När timertjänsten exekveras och når antalet två, skickas undantaget till komponenten Razor, vilket i sin tur utlöser felgränsen för att visa felinnehållet i <ErrorBoundary> inom komponenten MainLayout.

Åh, kära du! Åh! - George Takei