为啥我的 Blazor 服务器应用程序的计时器没有刷新其组件

Posted

技术标签:

【中文标题】为啥我的 Blazor 服务器应用程序的计时器没有刷新其组件【英文标题】:Why Is My Blazor Server App's Timer Is Not Refreshing Its Components为什么我的 Blazor 服务器应用程序的计时器没有刷新其组件 【发布时间】:2021-12-29 23:35:52 【问题描述】:

我完全重写了这个问题,因为对任何人都没有任何意义 - 我为这些问题道歉。

首先,我有一个名为 LocalSystemService 的 Singleton 服务,它处理 Blazor 服务器应用程序与在单独服务器上运行的单独 Web API 系统之间的 RESTful 通信。我已使用以下调用将该服务添加到我的 Blazor 应用程序中:

services.AddScoped<ILocalSystemService, LocalSystemService>();

我现在已经从一个简单的计时器转移到一个单独的服务,以响应我读过的其他文章。此服务称为 CheckLDC,并使用以下内容进行注册:

services.AddSingleton<CheckLDC>();

该服务的构造如下:

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using System.Web.UI;
using System.Web.UI.htmlControls;
using Telerik.Blazor;
using Telerik.Blazor.Components;
using Frontend.Data;
using Frontend.Services;

namespace Frontend.Services

    public class LDCExecutedEventArgs : EventArgs  
    

    public class CheckLDC : IDisposable
    
        public event EventHandler<LDCExecutedEventArgs> JobExecuted;
        void OnJobExecuted()
        
            JobExecuted?.Invoke(this, new LDCExecutedEventArgs());
        

        #region Globals
        static ReaderWriterLock locker = new ReaderWriterLock();
        private System.Timers.Timer checkRemoteData;
        private bool _Running;
        [Inject]
        public ILocalSystemService LocalSystemService  get; set; 
        [Inject]
        public ILogger<CheckLDC> _logger  get; set; 
        [Inject]
        public IMemoryCache _cache  get; set; 
        private const string LocationCacheName = "LocalSystem";
        #endregion

        public void StartExecuting()
        
            if (!_Running)
            
                // Initiate a Timer
                checkRemoteData = new System.Timers.Timer(1000);
                checkRemoteData.Elapsed += HandleTimer;
                checkRemoteData.AutoReset = true;
                checkRemoteData.Enabled = true;

                _Running = true;
            
        

        private async void HandleTimer(object source, ElapsedEventArgs e)
        
            **This call results in a NULL for the LocalSystemService!!!**
            **if (LocalSystemService.IsThereAnUpdate().Result)**
            
                await Task.Run(() =>
                
                    try
                    
                        locker.AcquireWriterLock(int.MaxValue);

                        if (!_cache.TryGetValue(LocationCacheName, out transferSystem))
                        
                            //We need to grab everything:
                            JsonSerializer serializer = new JsonSerializer();

                            #region Location
                            try
                            
                                _cache.Set(LocationCacheName, LocalSystemService.GetLocalSystem().Result);
                            
                            catch (Exception locWriteX)
                            
                                _logger.LogError("Failed to restore location locally with the error: " + locWriteX.ToString());
                            
                            #endregion
                        
                    
                    finally
                    
                        locker.ReleaseWriterLock();
                    
                );
            

            // Notify any subscribers to the event
            OnJobExecuted();
        
        public void Dispose()
        
            if (_Running)
            
                checkRemoteData?.Dispose();
            
        
    

然后我把这段代码放在我的主要组件后面

    [Inject]
    public ILocalSystemService LocalSystemService  get; set; 
    [Inject]
    public CheckLDC CheckTheBackend get; set;

请注意,我使用内存缓存来跨请求存储数据。注入到位后,我的 OnInit 方法如下所示:

protected override async Task OnInitializedAsync()
    
        await Task.Run(() =>
        
            //This call uses the LocalSystemService to grab and store the main data class into the cache 
            CurrentSystem = CreateCaches().Result;
        );
        //These are my event subscriptions
        CheckTheBackend.JobExecuted += HandleJobExecuted;
        CheckTheBackend.StartExecuting();
    

最后,在 JobExecute 上被调用的方法是:

public async void HandleJobExecuted(object sender, LDCExecutedEventArgs e)
    
        await InvokeAsync(StateHasChanged);
    

现在我在尝试从 CheckLDC 的 HandleTimer 事件调用中调用 LocalSystemService 时遇到 NULL 异常 - 我粗体键入了继续失败的调用。我已经为 LocalSystemService 尝试了 AddTransient 和 AddScope,但没有任何效果。在 Blazor 应用程序中,我可以毫无问题地调用 LocalSystemService - 但在 CheckLDC 单例中总是失败。

有什么想法吗?

【问题讨论】:

您必须提供一些代码。使用 Timer 和 StateHasChanged 制作模型。用 Task.Delay 或其他东西替换 API。见minimal reproducible example 没有任何代码可以使用,请参阅我最近在刷新 FetchData 中的 WeatherForecast 列表时回答的这个问题 - ***.com/questions/69967013/… 抱歉没有包含代码!我已经逐步完成并观察了每个 StateHasChanged 方法在我希望它们被调用的时候被准确地调用。无论我等待多久,它都不会改变 UI。 我为 CheckLDC 服务添加了一个接口,将两个服务的服务调用更改为 'services.AddScoped();' 'services.AddScoped();'而且我仍然在此调用期间收到 LocalSystemService 为空的错误:'private async void HandleTimer(object source, ElapsedEventArgs e)' '' 'if (LocalSystemService.IsThereAnUpdate().Result)' 【参考方案1】:

您不能在此处使用属性注入 - 仅适用于组件。

CheckLDC 需要使用构造函数注入

private readonly ILocalSystemService LocalSystemService;
private readonly ILogger<CheckLDC> _logger;
private readonly IMemoryCache _cache;

public CheckLDC(ILocalSystemService localSystemService,
  ILogger<CheckLDC> logger,
  IMemoryCache cache)

  LocalSystemService = localSystemService;
  _logger = logger;
  _cache = cache;

【讨论】:

【参考方案2】:

抱歉,如果没有完整的可重现代码示例,就很难遵循执行流程。但是,基于 FetchData 页面的以下代码 sn-p 和您的代码(我试图对您的代码尽可能真实)有效。注释代码是多余的。复制并测试...然后尝试将其应用到您的程序中,并报告问题。

@page "/fetchdata"

@using WebApplication1.Data
@using System.Timers;

@inject WeatherForecastService ForecastService
@implements IDisposable

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from a service.</p>

@if (forecasts == null)

    <p><em>Loading...</em></p>

else

    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            
        </tbody>
    </table>


@code 
    private WeatherForecast[] forecasts;
    private static System.Timers.Timer checkRemoteData;


    protected override async Task OnInitializedAsync()
    
        // forecasts = await ForecastService.GetForecastAsync(DateTime.Now);

        //await Task.Run(() =>
        //
        forecasts = await ForecastService.GetForecastAsync(DateTime.Now); //.Result;
 // What is ShowModal? Are you setting it to true somewhere ?
 // Perhaps this is why you do not see the changes, if you design 
 // to show the changes in a window modal                                                                             

 //  ShowModal = false;
                                                                          //);

        //This works and calls the main method every second
        checkRemoteData = new System.Timers.Timer(4000);
        checkRemoteData.Elapsed += OnTimedEvent;
        checkRemoteData.AutoReset = true;
        checkRemoteData.Enabled = true;
    

    private async void OnTimedEvent(Object source, ElapsedEventArgs e)
    
        //if (LocalSystemService.IsThereAnUpdate().Result)
        //
              
              // await InvokeAsync(StateHasChanged);
              // I have used the same call as in the OnInit - niether one works!
             forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
             await InvokeAsync(() => StateHasChanged()); //.ConfigureAwait(false);
              // What is this for ? Are you trying to emulate 
              // delay? Just increase the interval (to 4000...)
             // await Task.Delay(500);
             // Not needed
             // await InvokeAsync(StateHasChanged);
        //

        
    

    public void Dispose()
    
        checkRemoteData.Elapsed -= OnTimedEvent;
    

  

更新:

我已将以下方法添加到 WeatherForecastService 以模拟 LocalSystemService.IsThereAnUpdate().Result

 public Task<bool> IsThereAnUpdate()
 
      return Task.FromResult( true);
 

还添加了这个:

if (ForecastService.IsThereAnUpdate().Result)


 

现在当 IsThereAnUpdate 返回 true 时,UI 会更新,而当它返回 false 时则不会。

【讨论】:

对不起,多余的代码 - ShowModal 处理模式窗口,可以忽略。在您的情况下,ForecastServer 类似于我的 LocalSystemService,您在 OnTimedEvent 中看到的其余代码只是我添加越来越多的尝试来刷新 UI。我将编辑问题以澄清问题,因为您的解决方案应该有效,但它没有。 @Ken,我的代码示例确实有效。这是肯定的。请参阅我的答案中的更新部分。

以上是关于为啥我的 Blazor 服务器应用程序的计时器没有刷新其组件的主要内容,如果未能解决你的问题,请参考以下文章

为啥发布到 IIS 后我的 Blazor 服务器页面为空?

定时器停止更新页面的 Blazor Server 问题

Blazor DI 在 .razor 和商务类中的工作方式不同。为啥?

在android中,为啥我的服务在按下电源按钮后没有进入睡眠状态

为啥 Blazor 使用 post 而不是 WebSockets?

为啥“InvokeAsync”在 Blazor 组件中显示为错误?