Home > Enterprise >  C# BackgroundWorker with Library
C# BackgroundWorker with Library

Time:05-22

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

  • Related