-1

I had tried background worker, thread and task but the procedure are so long to get a solution according to my question. suppose I use a task, I can't add any textboxes in ABC function which is called in button_click. When I use textbox inside button click function on the place of ABC function it's worked. But this process is not fulfil my requirement. If I called this function directly into buttonclick event it's hang my UI. Here is the example code of ABC function.

public void ABC()
        {
            While(true)
            {
              richTextBox1.AppendText("Hello");
              richTextBox2.AppendText("Tesing");
            }
        } 

If anyone have an idea to achieve this, please tell me.

I am trying this because my sensor gives me data on continuous basis and I used these values to draw a graph, so I tried to run that in background to update my graphs on continuous basis.

Wait let me explain I have sensor which sends there data on API every 5 minutes in JSON format. Now my code fetching data from API and display a graph after button click. When I click on button my graph is updated with new data but I want to try to convert this manual process into automatic process. I mean when I click on button my function should have run continuously and update data on graph without hanging UI because I have to perform another operations on graph. Here is my function with original code:
public void EnvlopGraph() {

        //while (true)
        //{
            graph = new Graph();
            //frmdeviceconfig deviceinfo = new frmdeviceconfig(this);
            grpboxselectaxis.Enabled = false;
            chkboxxaxis.Checked = true;
            chkboxyaxis.Checked = false;
            chkboxzaxis.Checked = false;
            frm.GetSensorId();
            frm.GetOverall();
            chartview1.Visible = true;
            grpboxselectaxis.Visible = true;
            double[] XAxisRange;

            envlpxfft.Clear();
            envlpyfft.Clear();
            envlpxtime.Clear();
            envlpytime.Clear();
            var client = new RestClient(frm.ClientURL);
            var request = new RestRequest($"api/traces", Method.GET);

            try
            {
                IRestResponse response = client.Execute(request);

                JsonDeserializer deserial = new JsonDeserializer();
                dynamic obj = deserial.Deserialize<dynamic>(response);

                var objindexcount = obj.Count;
                var TracesData = obj[objindexcount - 1];    //this is applicable on current Bearing data only
                //var TracesData = obj[objindexcount - 2];      // for current Tri_Acc Data (x,y,z)

                var SerialNumber = TracesData["serialNumber"];
                if (SerialNumber == frmdeviceconfig.stpointsensor)
                {
                    var RouteTime = TracesData["routeTime"];
                    var SampleTime = TracesData["sampleTime"];
                    var Name = TracesData["name"];
                    var SignalPath = TracesData["signalPath"];
                    var Range = TracesData["range"];
                    string SampleRate = TracesData["sampleRate"];
                    string[] SR = SampleRate.Split('S', 'a', 'm', 'p', 'l', 'e', 'R', 'a', 't', 'e', '_');
                    double SampleRateVal = Convert.ToDouble(SR[11]);
                    string TraceLength = TracesData["traceLength"];
                    var XAxisTrace = TracesData["xAxisTrace"];
                    string[] TL = TraceLength.Split('T', 'r', 'a', 'c', 'e', 'L', 'e', 'n', 'g', 't', 'h', '_');
                    double TraceLengthVal = Convert.ToDouble(TL[12]);
                    double xtimerange = Convert.ToDouble((TraceLengthVal / TraceLengthVal) / TraceLengthVal);
                    if (chartview1.Controls.Count > 0)
                    {
                        chartview1.Controls.Clear();
                        chartview1.Controls.Remove(graph);
                    }

                    if (XAxisTrace != null)
                    {
                        var XAdcRangePercent = XAxisTrace["adcRangePercent"];
                        var XClipped = XAxisTrace["clipped"];
                        var XRangeApplied = XAxisTrace["rangeApplied"];
                        var XGravityRemoved = XAxisTrace["gravityRemoved"];
                        var XTimeWave = XAxisTrace["timewave"];
                        var xaxisdatalength = XTimeWave.Count;
                        XData = new double[xaxisdatalength];
                        XAxisRange = new double[xaxisdatalength];

                        for (int i = 0; i < xaxisdatalength; i++)
                        {
                            XData[i] = XTimeWave[i];
                            XAxisRange[i] = i * xtimerange;
                        }
                        //Add graph
                        envlpxtime.Add(XAxisRange);
                        envlpytime.Add(XData);
                        if (Display == 1)
                        {
                            if (GraphType != "FFT")
                            {
                                graph.DrawLineGraph(envlpxtime, envlpytime);
                            }
                            else
                            {
                                //fft graph for x axis 
                                FFTConversion fft = new FFTConversion();
                                if (XData == null)
                                {
                                    MessageBox.Show("TimeData is not available");
                                }
                                else
                                {
                                    double[] fftdata = fft.ConvertTimeDataToFFT(XData);
                                    chartview1.Visible = true;
                                    string[] linekhz = Name.Split('B', '_', 'X', 'R', 'G', 'L', 'H');
                                    double lines = Convert.ToDouble(linekhz[8]);
                                    double hz = Convert.ToDouble(linekhz[10]);
                                    double fftXrange = hz / lines;
                                    for (int k = 0; k < fftdata.Length; k++)
                                    {
                                        XAxisRange[k] = k * fftXrange;
                                    }

                                    envlpxfft.Add(XAxisRange);
                                    envlpyfft.Add(fftdata);
                                    graph.DrawLineGraph(envlpxfft, envlpyfft);
                                }
                            }
                            graph.Dock = DockStyle.Fill;
                            chartview1.Controls.Add(graph);
                        }

                    }
                    else
                    {
                    }
                }
            }
            catch (Exception ex)
            {
            }
    }
                                                                                         
  • 3
    The correct approach is to use a [`System.Windows.Forms.Timer `](https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.timer?view=windowsdesktop-6.0). Be sure to use this one and **not** one of the [other four](https://learn.microsoft.com/en-us/dotnet/api/system.threading.timer?view=net-6.0#remarks) –  Feb 04 '22 at 07:27
  • Take a look at [this example](https://stackoverflow.com/a/40415855/3110834). – Reza Aghaei Feb 04 '22 at 10:31
  • 1
    You should post the actual code. How *heavy* are the operations you need to perform for each *iteration*? If you just need to set the text of a couple of Controls and nothing else, a Timer is OK. If you need to elaborate data or wait for the response of a third-party API, it's not: you'll block the UI anyway, when the Timer's event is raised in the UI Thread (as it happens if you use the Timer suggested by @MickyD). You can run a Task instead, passing an `IProgress` delegate that only receives the data to show in the UI. The delegate executes in the UI Thread. – Jimi Feb 04 '22 at 11:43
  • You cannot *flood* the UI with data updates anyway, so you need to *time* the response from the running Task in any case. You can add a delay between each result awaiting `Task.Delay()`, set to, e.g., 200ms. -- A threaded Timer is harder to handle, since, if you block it's event handler, the Timer *ticks* anyway, raising the event in different Threads. You can suspend it, of course, but this causes synchronization issues (e.g., you update the UI at different intervals). – Jimi Feb 04 '22 at 11:47
  • Please never ever ever write `catch (Exception ex) { }`. It just swallows errors and makes your code more buggy. You should only ever catch ***specific*** exceptions that you can ***meaningfully*** handle. My suggestion is to remove all try catch blocks and only re-introduce them if you can meet the specific and meaningful criteria. – Enigmativity Feb 07 '22 at 01:49

2 Answers2

0

I believe that there is a better way, but sometimes you can't just change what is already there… In those cases, I use the Producer/Consumer Pattern for problems that are similar to yours.


1) Create the ProducerConsumer class.

public class ProducerConsumer<T>
{
    private readonly BlockingCollection<T> mCollection = new BlockingCollection<T>(100);
    private readonly Action<T> mConsumer;
    private readonly object SYNC_LOCK = new object();

    public ProducerConsumer(Action<T> consumer) {
        mConsumer = consumer;
    }

    public void Produce(T obj) {
        mCollection.Add(obj);
    }

    // If using multiple producers
    public void ThreadSafeProduce(T obj) {
        lock (SYNC_LOCK ) {
            Produce(obj);
        }
    }

    private void Loop() {
        while (true) {
            var obj = mCollection.Take();
            if (obj == null) return;

            mConsumer(obj);
        }
    }

    public void Start() {
        Task.Run(() => Loop());
    }
}

2) Example of how to use it

public partial class Form1 : Form
{
    private ProducerConsumer<string> mProducerConsumer;

    public Form1()
    {
        InitializeComponent();

        mProducerConsumer = new ProducerConsumer<string>(Consumer);
        mProducerConsumer.Start();

        FakeSensorReadings();
    }

    private void FakeSensorReadings() 
    {
        Task.Run(() => 
        {
            while (true)
            {
                mProducerConsumer.Produce("Teste string\r\n");
                Thread.Sleep(100);
            }
        });
    }

    private void Consumer(string str) {
        // Run on Main Thread
        this.Invoke(new MethodInvoker(() => 
        {
            textBox1.Text += str;
        }));
    }
}

HINT

You can use any object type that you want, like int, float, struct, custom objects, etc.

D.Kastier
  • 2,640
  • 3
  • 25
  • 40
0

So you have a Sensor class that produces data, and you have another class that processes (= consumes) the produced data. Sometimes the Producer produces more data than the Consumer can process, other times the Consumer has enough time to process the produced data.

A problem like this screams for the Producer-Consumer pattern: a Producer produces data that a Consumer processes in a different tempo: sometimes the Producer is faster, sometimes the Consumer is faster.

For this, Microsoft came up with the Dataflow Task Parallel Library (TPL). You can download the code using the nuget package manager.

Let's assume your Sensor produces a stream of SensorData, sometimes faster, sometimes slower.

Your Consumer process needs to take this Sensor data and process it. While this data is being processed, the Sensor might produce new SensorData. This SensorData needs to be buffered.

This buffering is done using the TPL BufferBlock. Whenever your Sensor produces some SensorData it Adds the data to the BufferBlock, and starts Producing the next SensorData.

Meanwhile, the Consumer does nothing but wait until there is some SensorData in the BufferBlock. As soon as it arrives, it removes it from the BufferBlock and processes the SensorData. When finished processing, it waits until there is SensorData again.

So if the Producer is (temporarily) faster than the Consumer, then there will be several SensorData in the BufferBlock. If the Consumer is faster, the BufferBlock will regularly be empty.

This process stops when the Sensor knows that there is no more data. It notifies the BufferBlock. When all SensorData in the BufferBlock are processed, the Consumer is notified that no more data is to be expected.

class SensorData {...};

class Sensor
{
    // send Produced SensorData to this ITargetBlock:
    public ITargetBlock<SensorData> DataBuffer {get; set;}

    private SensorData ReadSensor() {...}

    public async Task ProduceDataAsync(CancellationToken token)
    {
        // TODO: check if this.TargetBuffer and token not null

        while (!token.IsCancellationRequested)
        {
            SensorData sensorData = this.ReadSensor();
            await this.TargetBuffer.SendAsync(token);
        }

        // if here: cancellation requested. Stop producing SensorData
        // notify that no data will be produced anymore
        this.TargetBuffer.Complete();
    }
}

The Consumer:

class Consumer
{
     // the Consumer will listen for data here:
     public ISourceBlock<SensorData> DataBuffer {get; set;}

     private void ProcessSensorData(SensorData sensorData) {...}

     public async Task ConsumeDataAsync()
     {
          // TODO: check if DataBuffer

          // as long as there is SensorData expected: wait for it and process
          while (await this.DataBuffer.OutputAvailableAsync())
          {
              // There is some data; fetch it and process it
              SensorData sensorData = await this.DataBuffer.ReceiveAsync();
              this.ProcessSensorData(sensorData);
          }

          // if here: no more SensorData expected
    }
}

Putting it all together

BufferBlock<SensorData> buffer = new BufferBlock<SensorData>();
Sensor sensor = new Sensor
{
    DataBuffer = buffer,
}
Consumer consumer = new Consumer
{
    DataBuffer = buffer,
}

Now everything is ready to be started. For instance when pressing a button:

async void OnButtonStart_Clicked(object sender, ...)
{
    this.buttonStart.Enabled = false; // to prevent starting again
    this.buttonStop.Enabled = true;   // so we can stop the process

    await this.StartProcessingAsync();

    this.buttonStop.Enabled = false;
    this.buttonStart.Enabled = true;
}

private CancellationTokenSource CancellationTokenSource {get; set;}

async Task StartProcessingAsync()
{
    using (this.CancellationTokenSource = new CancellationTokenSource())
    {
        CancellationToken token = this.CancellationTokenSource.Token;

        // start Producing and Consuming:
        Task producerTask = sensor.ProduceDataAsync(token);
        Task consumerTask = consumer.ConsumeDataAsync();

        // continue producing and consuming until both tasks are finished
        await Taks.WhenAll(new Task[] {producerTask, consumerTask});
    }
}

To stop the process:

async void StopButton_ClickedAsync(object sender, ...)
{
    this.NotifyProducerBeingCancelled();   // notify the operator

    // Start the cancellation process
    this.CancellationTokenSource.Cancel();
} 

Cancelling the CancellationTokenSource will notify all Tokens from this Source. The Producer has such a token. It will check regularly whether cancellation is requested. If so, it will stop producing SensorData and notify the Buffer that no data is to be expected anymore. The method will return.

Meanwhile the Consumer is happily consuming SensorData. If all Produced SensorData has been processed, it sees that no more data is to be expected. The method will return.

Method StartProcessingAsync was waiting until both tasks ProduceDataAsync and ConsumeDataAsync are finished. When both are finished it will Enable and Disable the buttons again and return.

If the operator clicks the Stop Button several times before the Producer and Consumer are stopped, then only the CancellationTokenSource is cancelled again, which won't do anything.

Note that this whole thing is done by one thread. Or to be more accurate: only one context is used, this means that it is as if only one thread does everything. Therefore there is no need for you to protect using mutexes or semaphores. No need for InvokeRequired in method ProcessSensorData, because the thread has the UI context, and thus can update UI elements.

If the update of the UI elements was only as an example in your question, and you don't need to update UI elements at all, but for instance only save data in a file, consider to add ConfigureAwait(false) in your await statements

The Producer is

Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116
  • Wait let me explain I have sensor which sends there data on API every 5 minutes in JSON format. Now my code fetching data from API and display a graph after button click. When I click on button my graph is updated with new data but I want to try to convert this manual process into automatic process. I mean when I click on button my function should have run continuously and update data on graph. – kamlesh jha Feb 05 '22 at 05:40