0

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;
    }
Enigmativity
  • 113,464
  • 11
  • 89
  • 172
rlbb0206
  • 3
  • 1
  • You just have to assign `modbusclient` a valid instance. You can do that in the `DoWork` method or while you set up the background worker. – Enigmativity May 22 '22 at 08:05
  • OK. Can you help me understand and create this? I am currently familiarizing myself with C# and of course I would also like to understand how to create a valid instance. Thank you for your help. – rlbb0206 May 22 '22 at 08:16
  • 1
    You can't access windows controls from within the DoWork event because the thread that does the work is not the thread that created the windows control. Set `WorkerReportsProgress = true` on the worker, and raise a `ProgressChanged` event by calling `ReportProgress`. Pass the data you wish to use in the ProgressChanged event handler, to the overload of [ReportProgress that takes an Object state](https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.backgroundworker.reportprogress?view=net-6.0#system-componentmodel-backgroundworker-reportprogress(system-int32-system-object)) – Caius Jard May 22 '22 at 09:13
  • 2
    Please rename your controls after you drop them on a form. Code that is full of label14, button57, textBox38 is effectively obfuscated and difficult to read and debug; we don't want to do it and in 3 months time you won't want to either. – Caius Jard May 22 '22 at 09:26
  • @rlbb0206 - I just did. You either set it in the `DoWork` method or when you're setting up the background worker. Those are your two choices. Do also keep in mind that you cannot access, update, or create controls in the background thread. – Enigmativity May 22 '22 at 10:46

2 Answers2

0

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.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • I might be missing something, but I thought the issue was that `modbusclient ` is `null`? – Enigmativity May 22 '22 at 10:44
  • @Enigmativity maybe you are right. TBH I payed more attention to the fact that the labels are updated on the wrong thread. – Theodor Zoulias May 22 '22 at 10:59
  • This works very fine. I have insert a second Task but if i insert datatable.Rows.Add(value10, value11) the loop has no function. I become no errors in the Debugger. Is there any reason why? Should i ask this in a new thread? – rlbb0206 May 23 '22 at 20:08
  • @rlbb0206 any errors that may happen inside the `InfiniteLoopAsync` will cause the task to fail, and the error will be stored in the `_infiniteLoopTask.Exception` property. There will be no visual indication that something bad happened. If you want to inform the user about the error, you can enclose the code inside this method in a try/catch block, and popup a message box or update some label etc. Or you can do `await _infiniteLoopTask;` at the bottom of the `Form_Shown` handler, to expose the error in a standard way (that kills the process). – Theodor Zoulias May 23 '22 at 20:28
-1

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://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.timer?view=netframework-4.7.2

Henry Trần
  • 1,286
  • 12
  • 14