Blazor、带有异步方法和 IDisposable 的PeriodicTimer——这是正确的吗?

问题描述 投票:0回答:1

简短版本:以下内容对您来说线程安全吗?

以下伪代码是从工作代码简化而来的。 仅当 HTML 编辑器的内容发生更改时(我使用 TinyMCE),它才会每 10 秒更新一次 SQL 数据库条目。

我的问题是这样的——当原始页面的条件语句“擦除”

<MyEditor>
的实例时(当 SelectedItem 设置为 null 时),该实例会发生什么? 它是否会立即进行处理并一刀两断? periodictimer 或异步事件可能会抛出吗?

我的页面.razor

@if (SelectedItem is not null){
   <MyEditor @bind-HTML=SelectedItem.HTML @OnDeselect="()=>SelectedItem = null" />
}

@code {
    List<htmlItem> htmlItems = new(); // Assume I get these in init.
    htmlItem? SelectedItem;

    async Task SomeSelectionMethod(){
       SelectedItem = htmlItems.Single(hi=> somecriteria);
    }
}

我的编辑器.razor

@implements IDisposable

<MyMCE @bind-HTML=ActiveItem HTML @bind-HTML:after=HandleChangedHTML />
<button @onclick=DeselectMe > Return </button>
@code {
    [Parameter]
    public htmlItem ActiveItem {get; set;}

    [Parameter]
    public EventCallback OnDeselect {get; set;}

     private PeriodicTimer _timer;
     private CancellationTokenSource _cts = new();
     private bool NeedsSave;

     private async Task DeselectMe()
     {
         await SaveContentAsync();
         await OnDeselect.InvokeAsync();
     }
     protected override void OnInitialized()
     {
         _timer = new PeriodicTimer(TimeSpan.FromSeconds(10));
         _ = SaveContentPeriodically();
     }
     private async Task SaveContentPeriodically()
     {
         try
         {
             while (await _timer.WaitForNextTickAsync(_cts.Token))
             {
                 if (NeedsSave)
                 {
                 
                     await SaveContentAsync();
                     NeedsSave = false;
                 }

             }
         }
         catch (OperationCanceledException)
         {
             // Handle the cancellation
         }
     }
     private async Task SaveContentAsync()
     {
         await TS.UpdateHtmlItemAsync(ActiveItem); // worried about this. . . 
     }
     async Task HandleChangedHTML()
     {
         NeedsSave = true;
     }
     public void Dispose()
     {
         _cts.Cancel();
         _timer.Dispose();
     }
}
timer blazor thread-safety blazor-server-side periodictimer
1个回答
0
投票

首先介绍一些相关的背景信息。

幕后只有一个计时器。

TimerQueue
实现单例模式:每个 AppDomain 一个实例。它管理所有应用程序计时器并在计时器到期时安排回调。
TimerQueue
在它自己的线程上运行。 创建和销毁计时器并不昂贵。 大多数注册的计时器[在任何时间点]都将是操作超时:频繁创建和销毁,并且仅在出现问题时才触发。

基本上,无论您使用什么抽象类,它都会在

TimerQueue
上排队一个计时器对象,并在计时器到期时调用回调。

我的问题是这样的——当原始页面的条件语句“擦除”

<MyEditor>
的实例时(当
SelectedItem
设置为空时),该实例会发生什么?它会立即转到
Dispose
,并彻底决裂吗?
PeriodicTimer
或异步事件可能会抛出吗?

考虑一下你拥有什么:

  1. 内存中
    PeriodicTimer
    的一个实例。
  2. TimerQueue 上的计时器对象,引用
    _state.Signal()
    实例中的回调方法 [
    PeriodicTimer
    ]。
  3. WaitForNextTickAsync 实例中引用
    ValueTaskAwaiter
    [由
    PeriodicTimer
    提供] 的组件。

当渲染器从渲染树中删除

MyEditor
时,它会在组件上运行
Dispose
并释放对其的引用。 组件的
while
循环仍将保留对
ValueTaskAwaiter
的引用,直到完成并退出循环。

Dispose
上调用
PeriodicTimer
会执行以下操作:

public void Dispose()
{
    GC.SuppressFinalize(this);
    _timer.Dispose();
    _state.Signal(stopping: true);
}

_timer
TimerQueueTimer
。 处理它会将其从 TimerQueue 中删除 - 不再回调
_state.Signal()

_state
是一个内部
IValueTaskSource
类,提供对
ValueTask
的编程控制。
_state.Signal(stopping: true)
告诉
Signal
立即完成 ValueTaskAwaiter

ValueTask
完成时,组件中的 while 循环开始循环。 下一个

await _timer.WaitForNextTickAsync(_cts.Token)

返回

false
,因此循环退出并释放所有
ValueTask
引用。

我们现在在内存中有一个没有外部引用的组件实例和一个没有外部引用的

PeriodicTimer

一切都会顺利完成,GC 将销毁对象。

您可以在此处查看

PeriodicTimer
的源代码 - https://source.dot.net/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Threading/PeriodicTimer.cs ,d44c3c480b4836ad

© www.soinside.com 2019 - 2024. All rights reserved.