Home > Back-end >  Running two tasks simultaneously C# WinForms
Running two tasks simultaneously C# WinForms

Time:09-21

Hi I have a winform application which consists of 25 text boxes which represent values received in real time while they have labels beneath them which shows min max and average of these values flow in shown with red arrows

right now when application starts it populates data in each textbox and its label with a delay of 1 sec. So it takes 7 to 8 secs for it to completely populate the data.

what I want is to divide the task of filling text boxes and label into two portions.

I tried multithreading with it but it does not work for me

My code so far

        Thread mmathread;
        while (start)
        {
            try
            {
                RPM_TEXT.Text = ReadRPM().ToString()   " rpm";
                mmathread = new Thread(() => mma(rpm_list, ReadRPM(), RPM_mma));
                mmathread.Start();
                
            }
            catch { }

            try { EL_TEXT.Text = ReadEngineLoad().ToString()   " %";
                mmathread = new Thread(() => mma(el_list, ReadEngineLoad(), EL_mma));
                mmathread.Start();
                
            }
            catch { }
            try { ECT_TEXT.Text = ReadCoolantTemp().ToString()   " °C";
                mmathread = new Thread(() => mma(ect_list, ReadCoolantTemp(), ECT_mma));
                mmathread.Start();
                }
            catch { }
            }

The function definition is

private void mma(List<int> parameter_list, int parameter, Label label)
            {
            if (parameter_list.Count == 9) { parameter_list.Add(parameter); 
            parameter_list.RemoveAt(0); }
            else { parameter_list.Add(parameter); }
            minpara = parameter_list.Min();
            maxpara = parameter_list.Max();
            avrgpara = parameter_list.Sum() / parameter_list.Count;

            label.Text = $"Min = {minpara}, Avg = {avrgpara}, Max = {maxpara}";
            }

If i call the function directly it updates the label but if i add thread to it the label does not populate

CodePudding user response:

When working with Threads you need to use Invoke on the label. Like this

 label.Invoke((MethodInvoker) delegate () {label.Text = $"Min = {minpara}, Avg = {avrgpara}, Max = {maxpara}"};

https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.control.invoke?view=net-5.0

CodePudding user response:

You could use Task<T>s instead of Threads¹. The Task.Run method offloads the supplied action to the ThreadPool, and the result of the action is later available through the Result property of the Task<T>. You can await the Task<T>s, so that the UI will remain responsive while the actions are running in the background. You can also start multiple tasks at once, so that the actions are invoked in parallel. In this case you can await all parallel tasks with the Task.WhenAll method. Example:

while (start)
{
    Task delayTask = Task.Delay(TimeSpan.FromSeconds(1));
    Task<string> task1 = Task.Run(() => ReadRPM());
    Task<string> task2 = Task.Run(() => ReadEngineLoad());
    Task<string> task3 = Task.Run(() => ReadCoolantTemp());
    await Task.WhenAll(task1, task2, task3);
    RPM_TEXT.Text = task1.Result;
    EL_TEXT.Text = task2.Result;
    ECT_TEXT.Text = task3.Result;
    await delayTask;
}

In this example I've added a Task.Delay task, to impose a maximum frequency of one loop per second.

This code must be placed inside a method (or event handler) having the async modifier, to enable the await operator.

¹ I am referring to the Thread class, not to the thread as a concept.

CodePudding user response:

Elaborating on @BojanB's answer, you can use the traditional WinForms InvokeRequired/Invoke pattern. The InvokeRequired property returns true if it's evaluated on the non-UI thread. If an Invoke call is required, then you just Invoke the same call (marshaling the call onto the UI thread) and it will complete. If you do it this way, then the same function can be called from either the UI thread or any other thread.

First I created these three helper functions:

private void SetTextBoxFromThread(TextBox textBox, string contents)
{
    if (InvokeRequired)
    {
        Invoke(new Action<TextBox, string>(SetTextBoxFromThread), new object[] { textBox, contents });
        return;
    }

    textBox.Text = contents;
}

private string ReadTextBoxFromThread(TextBox textBox)
{
    if (InvokeRequired)
    {
        return (string)Invoke(new Func<TextBox, string>(ReadTextBoxFromThread), new[] { textBox });
    }

    return textBox.Text;
}
private void SetLabelFromThread(Label label, string contents)
{
    if (InvokeRequired)
    {
        Invoke(new Action<Label, string>(SetLabelFromThread), new object[] { label, contents });
        return;
    }

    label.Text = contents;
}

With those in hand, I tested them by using this stupid little thread function (that matches an async version of the ThreadStart delegate):

private int isPaused = 0;       //used like a bool, int for clear atomicity

private async void ThreadFunc()
{
    while (isPaused == 0)
    {
        var now = DateTime.Now;
        var str1 = now.ToString("T");
        var str2 = now.ToString("t");
        var str3 = now.ToString("s");

        SetTextBoxFromThread(textBox1, str1);
        SetTextBoxFromThread(textBox2, str2);
        SetTextBoxFromThread(textBox3, str3);

        await Task.Delay(1500);

        SetLabelFromThread(label1, ReadTextBoxFromThread(textBox1));
        SetLabelFromThread(label2, ReadTextBoxFromThread(textBox2));
        SetLabelFromThread(label3, ReadTextBoxFromThread(textBox3));
    }
}
  • Related