Blazor wasm 中的定期后台任务

Periodic background task in Blazor wasm

在 Blazor wasm 中,我想定期执行一个作业(代码), 即使用户正在浏览页面(例如每 x 分钟)。

这可能吗?什么是实用的方法?

创建一个服务来管理计时器

public class JobExecutedEventArgs : EventArgs {}


public class PeriodicExecutor : IDisposable
{
    public event EventHandler<JobExecutedEventArgs> JobExecuted;
    void OnJobExecuted()
    {
        JobExecuted?.Invoke(this, new JobExecutedEventArgs());
    }

    Timer _Timer;
    bool _Running;

    public void StartExecuting()
    {
        if (!_Running)
        {
            // Initiate a Timer
            _Timer= new Timer();
            _Timer.Interval = 300000;  // every 5 mins
            _Timer.Elapsed += HandleTimer;
            _Timer.AutoReset = true;
            _Timer.Enabled = true;

            _Running = true;
        }
    }
    void HandleTimer(object source, ElapsedEventArgs e)
    {
        // Execute required job

        // Notify any subscribers to the event
        OnJobExecuted();
    }
    public void Dispose()
    {
        if (_Running)
        {
            // Clear up the timer
        }
    }
}

在Program.cs

中注册
builder.Services.AddSingleton<PeriodicExecutor>();

首页初始化请求并启动

@page "/home"
@inject PeriodicExecutor PeriodicExecutor

@code {
    protected override void OnInitialized()
    {
        PeriodicExecutor.StartExecuting();
    }
}

在任何组件中如果你想在作业执行时做一些事情

@inject PeriodicExecutor PeriodicExecutor
@implements IDisposable

<label>@JobNotification</label>

@code {

   protected override void OnIntiialized()
   {
       PeriodicExecutor.JobExecuted += HandleJobExecuted;
   }
   public void Dispose()
   {
       PeriodicExecutor.JobExecuted -= HandleJobExecuted;
   }

   string JobNotification;
   void HandleJobExecuted(object sender, JobExecutedEventArgs e)
   {
        JobNotification = $"Job Executed: {DateTime.Now}";
   }
}

为此,您可以将 MainLayout.razor 或 App.razor 视为 'normal pages'。

当您有内容要在屏幕上显示时使用 MainLayout:

MainLayout.razor

.... 
<div>Time=@theTime</div>

@code
{
    protected override void OnInitialized()
    {
        Ticker();
        base.OnInitialized();
    }

    string theTime;

    // use an async-void or a timer. An async-void needs no cleanup. 
    async void Ticker()
    {
        while(true)
        {
            await Task.Delay(2_000);
            theTime = DateTime.Now.ToLongTimeString();

            StateHasChanged();   // refresh everything
        }
    }

}

如果您使用的是 ASP.NET 核心托管风格的 Blazor WebAssembly,则可以使用 BackgroundService。例如:

MyBackgroundService.cs

public class MyBackgroundService : BackgroundService, IDisposable
{
    private readonly ILogger<CollectionService> _logger;
    private readonly IServiceScopeFactory _serviceScopeFactory;

    public MyBackgroundService(ILogger<CollectionService> logger, IServiceScopeFactory serviceScopeFactory)
    {
        _logger = logger;
        _serviceScopeFactory = serviceScopeFactory;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("My Background Service is starting.");

        //Do your work here...

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<MyBackgroundService>();
    services.AddHostedService(provider => provider.GetService<MyBackgroundService>());

此解决方案的一个好处是,无论用户是否导航到站点上的任何特定页面,服务都将启动 运行。或者即使没有用户访问该站点。

在 Blazor 组件中最简单的方法是创建一个 Timer,订阅 Elapsed,然后调用 InvokeAsync 来更新任何状态:

// In a @code block, or a Component.razor.cs file:
private readonly Timer _jobTimer = new() 
{
    AutoReset = true,
    Enabled = true,
    Interval = 60000, // 1 minute
};

protected override void OnInitialized() {
    _jobTimer.Elapsed += JobMethod; 
    // ^ don't forget to unsubscribe and dispose in IDisposable.Dispose!
}

private async void JobMethod(object sender, ElapsedEventArgs e) {
    // do extraneous stuff here
    await InvokeAsync(() => {
        // update state here
        StateHasChanged();
    }
}

protected virtual void Dispose(bool disposing) {
    // however you implement this, but at the very least least:
    _jobTimer.Elapsed -= JobMethod;
    _jobTimer.Dispose();
}

如果作业已本地化为单个组件,请使用此选项。为了可重用性,更喜欢 .