带库的 C# BackgroundWorker

C# BackgroundWorker with Library

我已经集成了 EasyModBus 库,现在想使用后台工作程序通过 ModBus 每 250 毫秒查询一次值。消息 modbusclient is null 出现在后台工作程序中。如何在后台工作程序中获取 modbusclient 功能?有什么方法可以添加功能吗?

private void backgroundworker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
    {
        label10.Text = modbusclient.ReadInputRegisters(4, 1)[0].ToString() + " kHz"; //read register 300005 for frequency
        label11.Text = modbusclient.ReadInputRegisters(5, 1)[0].ToString() + " W"; //read register 300006 for power
        label12.Text = modbusclient.ReadInputRegisters(6, 1)[0].ToString() + " %"; //read register 300007 for amplitude in %
        TimeSpan t = TimeSpan.FromMilliseconds(Convert.ToDouble(modbusclient.ReadInputRegisters(8, 1)[0].ToString()));
        string runningtime = string.Format("{0:D2}m:{1:D2}s",
                            t.Minutes,
                            t.Seconds);
        label14.Text = runningtime;
    }

我的建议是放弃 anachronistic BackgroundWorker class, and use Task.Run and async/await。首先创建一个异步方法,每 250 毫秒连续循环一次并更新标签:

private async Task InfiniteLoopAsync(CancellationToken cancellationToken = default)
{
    while (true)
    {
        var delayTask = Task.Delay(250, cancellationToken);
        var value10 = await Task.Run(() => modbusclient.ReadInputRegisters(4, 1)[0]);
        var value11 = await Task.Run(() => modbusclient.ReadInputRegisters(5, 1)[0]);
        var value12 = await Task.Run(() => modbusclient.ReadInputRegisters(6, 1)[0]);
        var value14 = await Task.Run(() => modbusclient.ReadInputRegisters(8, 1)[0]);
        label10.Text = value10.ToString() + " kHz";
        label11.Text = value11.ToString() + " W";
        label12.Text = value12.ToString() + " %";
        TimeSpan t = TimeSpan.FromMilliseconds(Convert.ToDouble(value14));
        label14.Text = $"{t.Minutes:D2}m:{t.Seconds:D2}s";
        await delayTask;
    }
}

然后在表单首次显示时启动异步循环,最终在表单即将关闭时停止循环:

private CancellationTokenSource _cts = new();
private Task _infiniteLoop = Task.CompletedTask;

private void Form_Shown(object sender, EventArgs e)
{
    _infiniteLoopTask = InfiniteLoopAsync(_cts.Token);
}

private void Form_FormClosing(object sender, FormClosingEventArgs e)
{
    _cts.Cancel();
    // Wait the completion of the loop before closing the form
    try { _infiniteLoopTask.GetAwaiter().GetResult(); }
    catch (OperationCanceledException) { } // Ignore this error
}

重要的是,与 UI 控件的任何交互都只发生在 UI 线程上。仅当此代码既不读取也不更新 UI 组件时,您才能将代码卸载到 ThreadPool。使用 await Task.Run,您将获得卸载操作的结果,然后您回到 UI 线程,更新标签是合法的。

通过你的问题,我注意到winform平台的存在。

我的建议是您使用 Timer 实现。

它有点类似于您在上面尝试实现的 BackgroundWorker。不同之处在于,您将 Interval 属性 的值设置为 250,以便在 250 毫秒后 event Tick 中的代码将被触发为 运行。

此外,您还需要在每个事件执行之前添加条件if (modbusclient == null) return;行,这有助于您避免异常。

并且在开始 Timer.start() 之前不要忘记检查 modbusclient 是否已初始化!

参考:https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.timer?view=netframework-4.7.2