我有一个来自标准Blazor服务器端模板的非常简单的例子,它显示计时器函数即使在StateHasChanged((之后也不会更新UI;已拨打电话。
日志输出显示触发的timmer,如果我等待几秒钟并单击IncrementCount按钮,计数值会跳到计时器增加计数器的次数。
非常好奇。。。如有任何帮助,将不胜感激
致以亲切的问候,斯图尔特·
@page "/counter"
@using System.Timers;
@using Microsoft.Extensions.Logging
@inject ILogger<Counter> Logger
<h1>Counter</h1>
<p>Current count: @(currentCount.ToString())</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
public System.Timers.Timer timer;
protected override async Task OnInitializedAsync()
{
timer = new Timer(1000);
timer.Elapsed += this.OnTimedEvent;
timer.AutoReset = true;
timer.Enabled = true;
timer.Start();
}
public void OnTimedEvent(Object source, ElapsedEventArgs e)
{
Logger.LogInformation("Timer triggered");
IncrementCount();
StateHasChanged();
}
}
您正在运行Blazor服务器应用程序,对吗?在这种情况下,您应该从ComponentBase的InvokeAsync方法中调用StateHasChanged方法,如下所示:
InvokeAsync(() => StateHasChanged());
我想发生这种情况是因为计时器是在不同于UI线程的线程上执行的,这需要同步所涉及的线程。在Blazor WebAssembly上,这种行为不太可能发生,因为所有代码都在同一个UI线程上执行。
希望这能帮助。。。
Timer事件可以在后台线程上执行。当您的代码没有在正常的生命周期事件中运行时,请使用
InvokeAsync(StateHasChanged); // no () => () required.
此外,Timer类是IDisposable。因此添加:
@implements IDisposable
...
@code
{
...
public void Dispose()
{
timer?.Dispose();
}
}
说明:
计时器事件处理程序不应直接调用StatehasChanged()
。计时器事件是在默认(null(同步上下文上运行的池线程上处理的。
当您调用StatehasChanged((时,它将启动一个Render。渲染操作将调用Dispatcher.AssertAccess();
AssertAccess((的代码是
if (!CheckAccess()) throw new InvalidOperationException(...);
WebAssembly使用过载
public override bool CheckAccess() => true;
因此,在WebAssembly中,错误不会被注意到,但它仍然是一个错误。当WebAssembly将来获取线程时,此代码可能会开始失败。
对于服务器端,我们有
public override bool CheckAccess() => SynchronizationContext.Current == _context;
在服务器应用程序中,OP应该得到一个异常。也许记录器与没有发生的事情有关,我没有检查。