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

Posted

技术标签:

【中文标题】定时器停止更新页面的 Blazor Server 问题【英文标题】:Blazor Server Problem that page update by Timer stops 【发布时间】:2021-10-02 15:22:41 【问题描述】:

我编写了一个使用 Blazor 服务器应用定期更新页面的程序。

该程序执行时,每秒获取当前时间和数据库表中的值并显示在页面上,但该定时器中的处理在一定时间后停止。

虽然没有准确测量,但计时器中的处理会在大约 10 到 30 分钟后停止。

但是,当我查看浏览器的调试工具时,没有错误,在visual studio上也没有错误。

我搜索了一个这样的例子,但找不到它,也无法确定是什么原因造成的。

我希望此计时器中的处理工作,直到我明确指定它停止。

这可能是什么原因? 我有麻烦,因为我不知道原因。 请给我一些建议。 谢谢。

    private string Time  get; set; 

    protected override void OnInitialized()
    
        var timer = new System.Threading.Timer((_) =>
        
            Time = DateTime.Now.ToString();
            InvokeAsync(() =>
               
                // Get the value from the database
                databaseValue = TimerProcessGetValue();                
                StateHasChanged();
            );
        , null, 0, 1000);
        base.OnInitialized();
    

定时器中调用的进程

public async Task<int?> TimerProcessGetValue()

    int? timerProcessValue;
    using(DbContext = DbFactory.CreateDbContext())
    
        timerProcessValue = dbContext.TestTable.Single(x => x.Id == 1).TestValue;
    
    return timerProcessValue;

后记

目标组件的全文。

PageRefleshTest.cs

@page "/refresh-ui-manually"
@using System.Threading;
@using TestBlazorServer.Models;
@using Microsoft.EntityFrameworkCore;
@inject IDbContextFactory<SQLbeginnerContext> DbFactory

<h1>@Time</h1> 
<h1>TestValue:@databaseValue</h1>

private string Time  get; set; 
private int? databaseValue  get; set; 

protected override void OnInitialized()

    var timer = new System.Threading.Timer((_) =>
    
        Time = DateTime.Now.ToString();
        InvokeAsync(() =>
        
            // Get the value from the database
            databaseValue = TimerProcessGetValue().Result;
            StateHasChanged();
        );
    , null, 0, 1000);
    base.OnInitialized();



public async Task<int?> TimerProcessGetValue()

    int? timerProcessValue;
    using (var dbContext = DbFactory.CreateDbContext())
    
        timerProcessValue = dbContext.TestTable.Single(x => x.Id == 1).TestValue;
    
    return timerProcessValue;

使用 System.Timers.Timer 实现

@page "/Refresh2"
@inject IDbContextFactory<SQLbeginnerContext> DbFactory
@using TestBlazorServer.Models
@using Microsoft.EntityFrameworkCore
@using System.Timers
@implements IDisposable

<h3>PageRefresh2</h3>
<h1>@Time</h1>
<h1>@currentCount</h1>
<h1>TestValue:@databaseValue/h1>

@code 

private int currentCount = 0;
private Timer timer2 = new(1000);
private string Time  get; set; 
private int? databaseValue  get; set; 

protected override void OnInitialized()

    timer2.Elapsed += (sender, eventArgs) => OnTimerCallback();
    timer2.Start();


private void OnTimerCallback()

    _ = InvokeAsync(() =>
    
        currentCount++;
        Time = DateTime.Now.ToString();
        databaseValue = TimerProcessGetValue().Result;
        StateHasChanged();
    );



public void Dispose() => timer2.Dispose();


public async Task<int?> TimerProcessGetValue()

    int? timerProcessValue;
    await using (var dbContext = DbFactory.CreateDbContext())
    
        timerProcessValue = dbContext.TestTable.Single(x => x.Id == 1).TestValue;
    
    return timerProcessValue;

【问题讨论】:

你能说明databaseValue是如何定义的以及它是如何在组件中使用的吗? 不应该是 async () => databaseValue = await TimerProcessGetValue(); 10 到 30 分钟可能足以让信号器连接发生某些事情。我不确定我是否会进行任何编程,以便保持连接到服务器的浏览器对某些人来说至关重要进程成功 感谢您的 cmets。列出了目标组件的所有部分。这很简单,所以似乎没有问题......这是由于SignalR的行为,而不是程序?对不起,我不成熟,不太了解 SignalR。 【参考方案1】:

这当然可能是由于 SignalR 连接失败。不过,这应该会重新连接。

这里的主要嫌疑人是.Result。您应该始终避免在异步代码中使用 .Wait().Result。另外,您的数据库方法并不是真正的异步,这也可能起作用。

但是那个“半小时”也可能来自垃圾收集器抛出 Timer 对象。请参阅this page 上的紫色说明。当你有一个 Timer 时,你应该将它锚定在一个字段中并 Dispose() 它。

下一步是使事件处理程序成为async void,这是需要的极少数情况之一。然后只调用 StatehasChanged 调用。

@implements IDisposable

...
@code 
  System.Threading.Timer _timer;  // use a private field
  protected override void OnInitialized()
  
    _timer = new System.Threading.Timer(async (_) =>
    
        Time = DateTime.Now.ToString();
        databaseValue = await TimerProcessGetValue();
        await InvokeAsync(StateHasChanged);    
    , null, 0, 1000);
    // base.OnInitialized();  -- not needed
  

  public void Dispose() 
  
    _timer?.Dispose();
  

并使 Db 操作实际上是异步的:

public async Task<int?> TimerProcessGetValue()

    int? timerProcessValue;
    using (var dbContext = DbFactory.CreateDbContext())
    
        timerProcessValue = 
          (await dbContext.TestTable.SingleAsync(x => x.Id == 1))
          .TestValue;
    
    return timerProcessValue;

只有您可以现场测试,如果它有效,请告诉我们。

【讨论】:

非常感谢。我很快就会试试这个,我会告诉你结果。我还有一件事要担心。我最初尝试 System.Threading.Timer,这是原因吗?我想,并尝试使用 System.Timers.Timer 作为可能性之一来实现。 (我将代码放在问题中)在 System.Timers.Timer 中,程序启动已经过去了 4 个小时,但计时器中的处理正在正确处理。我意识到两者之间没有太大区别,您认为这与它有关吗? 哦,是的,这表明存在 GC 问题。我也会添加 Dispose。 非常感谢。应用修改后的代码。很抱歉重复了很多次,但我在 Invoke Async 部分收到了 CS4014 警告。我很抱歉用日语。 (在问题中添加了图片)程序可以正常运行,但这是一个警告,因为我没有使用等待 StateHasChanged? 这是我的错字。会编辑。但在这种情况下,它是无害的。 非常感谢。自从我开始该程序以来已经快 2 个小时了,但它运行良好。由于以前程序的计时器不规律地停止,看来垃圾收集器是原因,正如你所说。我无法想到这一点。我尊重你给出如此准确的答案。我自己肯定解决不了。我会仔细看看,但它可能没问题。对此,我真的非常感激。谢谢。

以上是关于定时器停止更新页面的 Blazor Server 问题的主要内容,如果未能解决你的问题,请参考以下文章

C# 为啥定时器会自动停止

如何将 returnUrl 传递到 Blazor Server 应用程序中的登录页面?

参数更新后 Blazor 页面未重新呈现

初始化 Blazor 组件时初始加载数据;页面更新时不要再次触发该查询

Blazor Server 每次使用的连接数受浏览器限制

javascript小实例,在页面中输出当前客户端时间