在 Blazor 中显示计时器

Display a timer in Blazor

我正在尝试在服务器端 Blazor 应用程序中显示倒数计时器。我的代码在 F# 和 C# 中。代码有些工作,但计时器永远不会按预期停止,并且计时器显示偶尔不会呈现所有数字。这是我第一次尝试 Blazor 服务器端应用程序。我不确定问题是异步问题、计时器问题还是渲染问题。

这是我的代码:

F#

let private setTimer countDown timeEvent =

    let timer = new Timer(float countDown * float 1000)
    let mutable time = 0

    time <- countDown

    timer.Elapsed.Add(fun arg ->
        time <- time - 1
        if time = 0
        then 
            timer.Stop()
            timer.Dispose()
        else
            ()
        timeEvent arg
    )

    timer.AutoReset <- true
    timer.Start()

let setTimerAsync countDown timeEvent = async{
    setTimer countDown timeEvent
    do! Async.Sleep (countDown * 1000)
}

type Timer (countDown) =

    member val CountDown : int = countDown with get,set

    member this.SetTimeAsTask (timeEvent) =
        setTimerAsync countDown timeEvent |> Async.StartAsTask

C#/Blazor

@page "/CountDown"
@using System.Timers
@using ClientTImer
@using Microsoft.FSharp.Core

<h3>Count Down</h3>
<p>
    Task: @task <br />
    Status: @status
</p>
<p>
    Timer: @time
</p>

@code {
    string task = "";
    string status = "";
    int time = 5;

    protected override async Task OnInitializedAsync()
    {

        // Initial task and status
        task = "First Task";
        status = "Status One";

        Action<System.Timers.ElapsedEventArgs> timeEvent =
            t =>
            {
                UpdateTime().Wait();
            };

        var func = FuncConvert.ToFSharpFunc(timeEvent);

        await new ClientTImer.Timer(time).SetTimeAsTask(func);

        // Update task and status
        task = "Second Task";
        status = "Status Two";
        await new ClientTImer.Timer(time).SetTimeAsTask(func);

        // Update task and status
        task = "Third Task";
        status = "Status Three";

    }

    public async Task UpdateTime()
    {
        await InvokeAsync(() =>
        {
            time--;
            StateHasChanged();
        });
    }

}

在你的 F# Timer.Elapsed 事件处理程序中,你的最后一行是 timeEvent (没有参数),我从你的代码的其余部分看到 timeEvent 是一个 Action 已转换为 F# 函数。由于您在 timeEvent 之后没有写入任何参数,该行所做的是将 timeEventvalue 指定为事件的 return 值处理程序,即您的事件处理程序正在 returning 一个函数。或者它 return 一个函数,如果事件处理程序 return 编辑了 void(或 F# 术语中的 unit)以外的东西。由于他们不这样做,我怀疑您在 timeEvent 行上收到了一条警告,其中说明了 timeEvent 的值在本质上被忽略了。

另外,我觉得 F# 中的 timer.Elapsed.Add 行不对。事件的 Add 方法接受类型为 'T -> unit 的参数,其中 'T 是事件为您提供的任何类型的数据:对于定时器的 Elapsed 事件,那将是一个 ElapsedEventArgs 实例。您应该传递给 Add 的是 fun elapsedEventArgs -> ...。然后你会改变你的 timeEvent 行来实际传递一个参数(那些相同的 elapsedEventArgs)以便它被调用并实际做一些事情。

此外,每当你递减一个数字并将其与 0 进行比较时,我总是喜欢将比较作为 <= 而不是 =,只是在我改变我的机会时稍后以可能导致递减发生两次的方式进行编码。如果我的比较是 = 0 并且双递减将数字从 1 减到 -1,则不会触发 if x = 0 分支。但是如果我是比较<= 0,那么即使我在其他地方犯了错误,它也会触发。所以我建议写 if time <= 0 而不是 if time = 0.

换句话说,我认为您的 timer.Elapsed 事件处理程序需要如下所示:

timer.Elapsed.Add(fun evtArgs ->
    time <- time - 1
    if time <= 0
    then 
        timer.Stop()
        timer.Dispose()
    else
        ()
    timeEvent evtArgs
)

rmunn 让我走上了正确的轨道,但我的逻辑有点偏离,因为我没有完全理解计时器 class 实际在做什么。我不仅要考虑开始时间,还要考虑间隔时间才能使所有内容同步,现在代码可以按预期工作。这是更新后的代码:

F#:

let private setTimer countDownSec intervalSec timeEvent =

    let timer = new Timer(float intervalSec * float 1000)
    let mutable time = 0

    time <- countDownSec

    timer.Elapsed.Add(fun arg ->
        time <- time - 1
        if time <= 0
        then 
            timer.Stop()
            timer.Dispose()
        else
            ()
        timeEvent arg
    )

    timer.AutoReset <- true
    timer.Start()

let internal setTimerAsync countDownSec intervalSec timeEvent = async{
    setTimer countDownSec intervalSec timeEvent
    do! Async.Sleep (countDownSec * 1000)
}

type Timer (countDown) =

    member val CountDown : int = countDown with get,set

    member this.SetTimeAsTask (timeEvent, interval) =
        setTimerAsync countDown interval timeEvent |> Async.StartAsTask

C#/Blazor:

@page "/CountDown"
@using System.Timers
@using ClientTImer
@using Microsoft.FSharp.Core
@using System.Threading

<h3>Count Down</h3>
<p>
    Task: @task <br />
    Status: @status
</p>
<p>
    Timer: @time
</p>

@code {
    string task = "";
    string status = "";
    int startCount = 5;
    int time;

    protected override async Task OnInitializedAsync()
    {
        time = startCount;

        // Initial task and status
        task = "First Task";
        status = "Status One";

        Action<System.Timers.ElapsedEventArgs> timeEvent =
            t =>
            {
                UpdateTime().Wait();
            };

        var func = FuncConvert.ToFSharpFunc(timeEvent);

        await new ClientTImer.Timer(startCount).SetTimeAsTask(func,1);

        // Update task and status
        task = "Second Task";
        status = "Status Two";
        await new ClientTImer.Timer(startCount).SetTimeAsTask(func,1);

        // Update task and status
        task = "Third Task";
        status = "Status Three";

    }

    public async Task UpdateTime()
    {
        await InvokeAsync(() =>
        {
            time--;

            if(time <= 0)
            {
                time = startCount;
            }

            StateHasChanged();
        });
    }

}