同时运行两个任务



嗨,我有一个winform应用程序,它由25个文本框组成,这些文本框代表实时接收的值,同时它们下面有标签,显示这些值的最小、最大值和平均值,用红色箭头显示

现在当应用程序启动时,它在每个文本框及其标签中填充数据,延迟1秒,因此它需要7到8秒才能完全填充数据。

我想把填充文本框和标签的任务分成两个部分。

我尝试了多线程,但它不适合我

我的代码到目前为止

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 { }
}
函数定义为
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}";
}

如果我直接调用这个函数,它会更新标签,但如果我给它添加线程,标签不会填充

当使用线程时,你需要在标签上使用Invoke。这样的

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

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

可以用Task<T>s代替Threads¹。Task.Run方法将提供的动作卸给ThreadPool,然后通过Task<T>Result属性获得动作的结果。您可以awaitTask<T>s,这样当操作在后台运行时UI将保持响应。您还可以一次启动多个任务,以便并行调用操作。在这种情况下,您可以使用Task.WhenAll方法await所有并行任务。例子:

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;
}

在这个例子中,我添加了一个Task.Delay任务,以施加每秒一个循环的最大频率。

此代码必须放置在具有async修饰符的方法(或事件处理程序)中,以启用await操作符。

¹我指的是Thread类,而不是线程作为一个概念。

详细说明@BojanB的答案,您可以使用传统的WinFormsInvokeRequired/Invoke模式。如果在非ui线程上计算InvokeRequired属性,则返回true。如果需要Invoke调用,那么只需Invoke相同的调用(将调用封送到UI线程上),它就会完成。如果你这样做,那么同样的函数可以从UI线程或任何其他线程调用。

首先,我创建了这三个辅助函数:

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;
}
有了这些,我通过使用这个愚蠢的小线程函数(匹配ThreadStart委托的async版本)来测试它们:

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));
}
}