I have integrated the EasyModBus Library and would now like to use the background worker to query values every 250ms via the ModBus. The message modbusclient is null appears in the background worker. How can I get the modbusclient function in the background worker? Is there any way to add a function?
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;
}
CodePudding user response:
My suggestion is to ditch the anachronistic BackgroundWorker
class, and use Task.Run
and async/await instead. First create an asynchronous method that will loop continuously every 250 msec and will update the labels:
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;
}
}
Then start the asynchronous loop when the form is first shown, and eventually stop the loop when the form is about to close:
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
}
It's important that any interaction with the UI controls happens exclusively on the UI thread. You can only offload code to the ThreadPool
only if this code neither reads or updates UI components. With the await Task.Run
you are handed the result of the offloaded operation, and you are back on the UI thread where it's legal to update the labels.
CodePudding user response:
Through your problem, I noticed the presence of winform platform.
My suggestion is that you implement using Timer.
It is somewhat similar to the BackgroundWorker you are trying to implement above. The difference is that you will set the value for the Interval
property to 250
so that after 250ms the code in event Tick
will be triggered to run.
In addition, you also need to add the conditional if (modbusclient == null) return;
line before every event's execution, this helps you avoid exceptions.
And don't forget to check that modbusclient
is initialized before starting to Timer.start()
!!
Reference: https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.timer?view=netframework-4.7.2