【问题标题】:Blazor Server System.ObjectDisposedException issues when updating pages更新页面时出现 Blazor Server System.ObjectDisposedException 问题
【发布时间】: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() 以某种方式引起的。

标签: c# blazor blazor-server-side


【解决方案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 键重新加载和常规处理工作正常。有几个我试过了,但我不知道是什么原因造成的......
猜你喜欢
  • 1970-01-01
  • 2018-06-06
  • 2021-12-25
  • 2020-09-12
  • 2015-08-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-11
相关资源
最近更新 更多