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 Thread
s¹. 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));
}
}