更新页面时出现 Blazor Server System.ObjectDisposedException 问题

Posted

技术标签:

【中文标题】更新页面时出现 Blazor Server System.ObjectDisposedException 问题【英文标题】:Blazor Server System.ObjectDisposedException issues when updating pages 【发布时间】:2021-11-26 05:31:00 【问题描述】:

在 Blazor Server 中添加了每秒更新数据的过程。

这工作正常,但是当我在浏览器中按刷新页面 (F5) 时,我收到以下错误:

System.ObjectDisposedException: 'Cannot process pending renders after the renderer has been disposed.
ObjectDisposed_ObjectName_Name'

目标代码在这里

@code 
  private List<Models.Recipe> recipes  get; set; 
  private List<Models.NowOrder> nowOrders  get; set; 
  private List<Models.PlanOrder> planOrders  get; set; 
  System.Threading.Timer _timer;

  protected override void OnInitialized()
  
    using (var dbContext = DbFactory.CreateDbContext())
    
        this.recipes = dbContext.Recipes.ToList();
        this.planOrders = dbContext.PlanOrders.ToList();
        this.nowOrders = dbContext.NowOrders.ToList();
    

    _timer = new System.Threading.Timer(async (_) =>
    
        Time = DateTime.Now.ToString();
        databaseValue = await TimerProcessGetValue();
        await InvokeAsync(StateHasChanged);    
    , null, 0, 1000);
  

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

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

刷新页面时“await InvokeAsync(StateHasChanged);”

如果注释掉以下部分,即使按F5键也不会报错,所以我认为我对异步处理的处理是错误的,但是我很困扰,因为我什至找不到相同的情况如果我搜索我的错误。

您认为这是什么原因? 感谢您的合作。

_timer = new System.Threading.Timer(async (_) =>

    Time = DateTime.Now.ToString();
    databaseValue = await TimerProcessGetValue();
    await InvokeAsync(StateHasChanged);    
, null, 0, 1000);

更改为验证码

        _timer = new System.Threading.Timer(async (_) =>
    
        Time = DateTime.Now.ToString();
        //databaseValue = await TimerProcessGetValue();
        await Task.Delay(500); //Added verification code.
        await InvokeAsync(StateHasChanged);    
    , null, 0, 1000);
  

追加。(2021 年 10 月 10 日)

使用的版本:net core 5.0.7。

使用的浏览器:Edge。

我没有使用任何其他浏览器。

我的环境有限,只能用Edge……

以下是我们已验证的三个代码。

①这是一个使用Dispose(WaitHandle)的例子。

这并没有改善错误。

public void Dispose()

    using(System.Threading.WaitHandle waitHandle = new System.Threading.ManualResetEvent(false))
    
        if (_timer.Dispose(waitHandle))
        
            const int millisecondsTimeout = 500;
            if (!waitHandle.WaitOne(millisecondsTimeout))
            
                System.Diagnostics.Debug.WriteLine("Dispose Test");
            
        
    

②这是一个使用 System.Timers.Timer 的例子。

使用 F5 键重新加载成功。

private int currentCount = 0;
private Timer timer2 = new(1000);

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

③增加错误处理。

即使使用这种方法,我也成功地使用 F5 键重新加载,没有任何错误。

    try
    
        _timer = new System.Threading.Timer(async (_) =>
        
            Time = DateTime.Now.ToString();
            databaseValue = await TimerProcessGetValue();
            await InvokeAsync(StateHasChanged);
        , null, 0, 1000);
    
    catch
    
        // empty on purpose
    

【问题讨论】:

你在标记部分使用@implements IDisposable吗? 你能通过将databaseValue = await TimerProcessGetValue(); 替换为Task.Delay(500); 来创建minimal reproducible example 吗?因为我无法重现此错误。用一个新的普通项目做到这一点。 抱歉信息不足。是的,我正在使用@implements IDisposable。 非常感谢。添加了验证码。如果我没有达到我想做的事,我很抱歉。但是,通过用这个 await Task.Delay (500) ; 替换它,可以正常执行页面重新加载。这是为什么... 这意味着它是由 TimerProcessGetValue() 以某种方式引起的。 【参考方案1】:

https://docs.microsoft.com/en-us/dotnet/api/system.threading.timer?view=net-5.0

当不再需要计时器时,使用 Dispose 方法释放计时器持有的资源。请注意,调用 Dispose() 方法重载后可能会发生回调,因为计时器将回调排队以供线程池线程执行。您可以使用 Dispose(WaitHandle) 方法重载来等待所有回调完成。

这就是为什么在释放组件后调用await InvokeAsync(StateHasChanged);

【讨论】:

这看起来像是解决方案,但我认为 DisposeAsync() 更适合这里。 抱歉迟到了。谢谢你的精彩回答。我没有经验,也不是完全可以理解,但是意识到调用Dispose时,它调用了StateHasChanged,它是在组件被销毁之前使用的,这可以吗?当然,我每次重新加载页面时都没有收到错误消息。 (虽然这几乎是一个错误......)【参考方案2】:

扩展@Brianparker 的回答:

@implements IDisposable 替换为@implements IAsyncDisposable

然后将 Dispose() 方法替换为

public ValueTask DisposeAsync()

    return _timer?.DisposeAsync() ?? ValueTask.CompletedTask;


更新

但我得到同样的错误

我无法重现您的错误,但我认为 F5 相当残酷。

您可以尝试在 StateHasChanged 调用周围放置一个 try/catch:

try

  await InvokeAsync(StateHasChanged);    

catch
  // empty on purpose

【讨论】:

抱歉迟到了。非常感谢您向我展示更正示例。这是一种我不熟悉的写作方式,所以我会尝试找出它的含义。我会尽快用您收到的内容进行更正。 我尝试了教我的代码,但我得到了同样的错误并且情况没有改善......我会再检查一下...... 嗯,我无法重现这个。您确定它会在禁用 TimerProcessGetValue 行的情况下发生吗?我添加了一个可能的补丁,试试看。 我永远感谢您的帮助。由于情况,我现在没有环境可以尝试,所以明天我会尝试。正如其他人所回答的那样,我今天尝试了 Dispose (WaitHandle)。我指的是这个链接。 (很抱歉不是英文的)C # programming commentary。但总而言之,这也没有改善错误。这只是个人猜测,但我认为它也受到system.threading.timer的影响,所以我明天用System.Timers.Timer试试。我马上告诉你结果。 抱歉这么晚了。我在问题中添加了我尝试过的代码。首先,Dispose (WaitHandle) 没有解决错误。正如我在调试时跟踪代码时所指出的那样,似乎在调用 Dispose 之后调用了 StateHasChanged。在 System.Timers.Timer 示例中,重新加载成功。正如我在最后告诉你的那样,我正在添加错误处理。 (如果描述方法有误,我很抱歉) 再次,用 F5 键重新加载和常规处理工作正常。有几个我试过了,但我不知道是什么原因造成的......

以上是关于更新页面时出现 Blazor Server System.ObjectDisposedException 问题的主要内容,如果未能解决你的问题,请参考以下文章

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

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

如何更改刷新 Blazor PWA 应用程序时出现的“正在授权...”消息?

尝试获取资源时出现 Blazor WASM NetworkError。 “CORS 缺少允许来源”

当用户在 Blazor Webassembly 身份验证和授权中具有多个角色时出现问题?

Cors Policy 问题 Blazor WASM、Web API 和 Identity Server 4 和 IIS