首页 > 解决方案 > 更新页面时出现 Blazor Server System.ObjectDisposedException 问题

问题描述

添加了在 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
    }

标签: c#blazorblazor-server-side

解决方案


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

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

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


推荐阅读